Version 1.2 of ai05s/ai05-0131-1.txt
!standard 12.6(10) 11-02-04 AI05-0131-1/02
!class binding interpretation 08-12-04
!status work item 08-12-04
!status received 08-10-23
!priority Low
!difficulty Medium
!qualifier Omission
!subject Class-wide operations for formal subprograms revisited
!summary
The equivalence defined in AI05-0071-1 for formal subprogram matching is
extended such that it applies to explicit as well as default actual
subprogram. The equivalence is also explicitly given use-visibility, so
that explicitly named, directly visible subprograms have preference over
the implicitly created equivalent subprograms.
!question
AI05-0071-1 allows an instantiation to automatically create a matching
subprogram for a class-wide type if a primitive for the specific type is
directly visible.
However, this requires using a use clause to get the proper visibility, which
can clutter the visibility with many unrelated subprograms. Moreover, the
capability is not available if the subprogram is directly specified in the
instantiation.
It is very unusual in Ada to be able to do something by default that you cannot
do explicitly.
Should this be corrected? (Yes.)
!recommendation
(See Summary.)
!wording
Add after 8.4(8.2):
Certain implicit declarations may become potentially
use-visible in certain contexts as described in 12.6.
Add after 12.6(9):
If a subtype_mark in the profile of the formal_subprogram_declaration
denotes a formal private or formal derived type and the actual type
for this formal type is a class-wide type T'Class, then for the
purposes of resolving the corresponding actual subprogram at the
point of the instantiation, certain implicit declarations
may be available as possible resolutions as follows:
For each primitive subprogram of T that is directly visible at
the point of the instantiation, and that has at least one controlling
formal parameter, a corresponding implicitly declared subprogram with
the same defining name, and having the same profile as the primitive
subprogram except that T is systematically replaced by T'Class
in the types of its profile, is potentially use-visible.
The body of such a subprogram is as defined in 12.5.1 for primitive
subprograms of a formal type when the actual type is class-wide.
AARM Implementation Note:
Although the above wording seems to require constructing implicit
versions of all of the primitive subprograms of type T, it should be
clear that a compiler only need to consider those that could possibly
resolve to the corresponding actual subprogram. For instance, if the formal
subprogram is a procedure with two parameters, and the actual subprogram
name is Bar (either given explicitly or by default), the compiler
need not consider primitives that are functions, that have the wrong
number of parameters, that have defining names other than Bar, and so on;
thus it does not need to construct implicit declarations for those
primitives.
Undo the 12.6(10) wording changes of AI05-0071, leaving only the original (i.e.,
pre-AI05-0071) first sentence:
If a generic unit has a subprogram_default specified by a box, and the
corresponding actual parameter is omitted, then it is equivalent to an
explicit actual parameter that is a usage name identical to the
defining name of the formal.
!discussion
Consider an example like:
package Pack1 is
type Root is tagged record
F1 : Integer;
end record;
procedure Oper_1 (X : in out Root);
end Pack1;
package Pack2 is
generic
type T(<>) is private;
with procedure Oper_1 (X : in out T) is <>;
package Gen_Pack is
end Gen_Pack;
end Pack2;
with Pack1;
with Pack2;
package Pack3 is
package Inst1 is new Pack2.Gen_Pack (Pack1.Root); --
package Inst2 is new Pack2.Gen_Pack (Pack1.Root'Class); --
package Inst3 is new Pack2.Gen_Pack (Pack1.Root, Pack1.Oper_1); --
package Inst4 is new Pack2.Gen_Pack (Pack1.Root'Class,
Pack1.Oper_1); --
use Pack1;
package Inst5 is new Pack2.Gen_Pack (Pack1.Root); --
package Inst6 is new Pack2.Gen_Pack (Pack1.Root'Class); --
end Pack3;
Inst1 is illegal because Oper_1 is not directly visible at the point of the
instantiation. Inst2 is illegal for a similar reason. The use clause makes
the similar Inst5 and Inst6 legal, the latter because of the new rules of
AI05-0071-1.
But if you want to explicitly give the subprogram (perhaps because the use
clause drags in lots of other conflicting names), Inst3 is legal (of course),
but Inst4 is not.
Ada almost always allows using selected notation rather than use visibility
(it even goes so far as to provide selection notation for operators!), but
that is not the case here. That seems to be a mistake.
----
AI05-0071-1 also introduced another problem (noticed while rewording to fix
the problem in the question). Since it doesn't explain where the implicit
subprogram is declared, it is not clear how it interacts with explicit
routines.
For instance, consider the following case where the instance would have been
legal without AI05-0071:
package Pkg1 is
type T1 is tagged null record;
procedure Foo (X1 ; T1);
generic
type T2 (<>) is private;
with procedure Foo (X2 : T2) is <>;
package G is
end G;
end Pkg;
with Pkg1;
package Pkg2 is
use Pkg1;
procedure Foo (X : T1'Class);
package I is new G (T1'Class); --
end Pkg2;
It is hard to figure out how the visibility rules apply to the above example
because the AI05-0071 wording just says that a "Foo" with the right profile "is
directly visible" without defining the declaration point (and thus, the scope)
of this "Foo".
In any case, it seems unfriendly to reject the above instantiation.
It seems clear what the user wants.
One could imagine an approach using (in effect) a preference rule, where the
implicit declaration is only referenced if it is needed. This would introduce
Beaujolais effects because adding/deleting a use clause could affect the need
for the implicit declaration.
Consider the variation of the above example where we replace
procedure Foo (X : T1'Class);
with
package Foo_Pkg is
procedure Foo (X : T1'Class);
end Foo_Pkg;
use Foo_Pkg;
and then consider the effect of deleting the "use Foo_Pkg;".
This illustrates how a preference rule could introduce Beaujolais effects.
To deal with this aspect of the problem, the proposed wording says
that the implicit declaration is not "directly visible", but rather
"potentially use-visible".
This is the only part of the wording changes that is motivated by this
"homograph collision" problem. The rest of the wording changes are motivated by
the problem that this AI was originally created to address.
It seems odd to introduce the possibility of a "potentially use-visible"
declaration in a program which does not contain the reserved word "use"
anywhere, but this seems to solve the problem and no better approach has been
found.
----
The original example which motivated AI05-0071 remains legal, but now this is
handled via the equivalence stated in the one sentence of 12.6(10) that has
remained constant throughout all of these changes (listed above), as opposed to
via an only-for-defaulted-parameters rule.
Note that this proposal follows AI05-0071 in building implicit declarations only
for primitive subprograms which have at least one controlling formal parameter
(as opposed to only a controlling result), AI05-0071 got this part right -
building on 12.5.1(23.3/2)'s weirdness seems like a bad idea. With at least one
controlling operand, we have a nice clean wrapper model. When we only have a
controlling result, things get messy.
Note that with the proposed changes, the
package Foo_Pkg is
procedure Foo (X : T1'Class);
end Foo_Pkg;
use Foo_Pkg;
version of the example described above will be rejected (which is a good thing -
we don't want Beaujolais effects).
It would also be rejected (for the same reason) if the actual parameter were
specified explicitly via a simple name, as in
package Foo_Pkg is
procedure Foo (X : T1'Class);
end Foo_Pkg;
use Foo_Pkg;
package I is new G (T1'Class, Foo);
but would be accepted if the name of the actual were qualified, as in
package Foo_Pkg is
procedure Foo (X : T1'Class);
end Foo_Pkg;
use Foo_Pkg;
package I is new G (T1'Class, Foo_Pkg.Foo);
(and note that adding/deleting the "use Foo_Pkg;" in this version of the example
would make no difference - that use clause has no effect on this example even if
it is present). This shows that there is a straighforward workaround to any
ambiguities created.
--!corrigendum 12.6(10/2)
!ACATS Test
An ACATS C-test should test the revised rule.
!appendix
!topic Should AI05-0071 allow renaming subprograms?
!reference AI05-0071
!from Adam Beneschan 08-10-23
!discussion
The language of AI05-0071 allows for a generic to be instantiated with a
class-wide type, and will allow the actuals for generic formal subprograms
to be dispatching subprograms "created" by the compiler, in a sense. However,
the language of AI05-0071 allows for this only if the operation is a primitive
subprogram that is directly visible.
I'm wondering whether this should be expanded a bit to allow renames.
Consider this example, which doesn't involve instantiation with class-wide
types:
package Pack1 is
type Root is tagged record
F1 : Integer;
end record;
procedure Oper_1 (X : in out Root);
end Pack1;
package Pack2 is
generic
type T(<>) is private;
with procedure Oper_1 (X : in out T) is <>;
package Gen_Pack is
end Gen_Pack;
end Pack2;
with Pack1;
with Pack2;
package Pack3 is
package Inst is new Pack2.Gen_Pack (Pack1.Root); -- ERROR:
end Pack3;
This won't compile, because for the generic formal subprogram default to be used,
Oper_1 would have to be visible. This can be accomplished with USE or USE TYPE.
But another way, if it is undesirable to make everything directly visible that
USE or USE TYPE would make visible, would be to use a rename:
package Pack3 is
procedure Oper_1 (X : in out Pack1.Root) renames Pack1.Oper_1;
package Inst is new Pack2.Gen_Pack (Pack1.Root);
end Pack3;
This option doesn't appear to be available in the class-wide type case:
package Pack3 is
procedure Oper_1 (X : in out Pack1.Root) renames Pack1.Oper_1;
package Inst is new Pack2.Gen_Pack (Pack1.Root'Class);
end Pack3;
The new language that AI05-0071 adds to 12.6(10) wouldn't apply because this
subprogram rename isn't a primitive subprogram, and the subprogram that it
renames, which is a primitive subprogram, is still not directly visible.
It may not be the worst problem in the world, and you can work around it, but it's
a little ugly:
package Pack3 is
package Dummy_Nested is
use type Pack1.Root;
package Inst is new Pack2.Gen_Pack (Pack1.Root'Class);
end Dummy_Nested;
package Inst renames Dummy_Nested.Inst;
end Pack3;
But should 12.6(10) as modified by this AI be modified to allow directly visible
subprograms that are renames of primitive subprograms, as well as directly visible
primitive subprograms?
****************************************************************
From: Adam Beneschan
Date: Thursday, October 23, 2008 12:34 PM
> This won't compile, because for the generic formal subprogram default
> to be used, Oper_1 would have to be visible. This can be accomplished
> with USE or USE TYPE.
Ummm, my bad. USE TYPE wouldn't work in the above case, but it would work
if the subprogram were an operator. This actually makes the problem potentially
a little worse than I thought, since it means that in the AI95-71 case, if the
subprogram isn't an operator, you'd have to use USE instead of USE TYPE to make
it directly visible, and there could be even more objection to that since it
could make many other things directly visible.
****************************************************************
From: Tucker Taft
Date: Thursday, October 23, 2008 12:42 PM
I don't see the need. This is a tricky enough implementation area as it is,
so trying to wedge renames into this seems like overkill. By the way, a
"use type" wouldn't help here because we aren't talking about an operator.
****************************************************************
From: Randy Brukardt
Date: Friday, October 24, 2008 7:39 PM
Those of us from the society of use-adverse programmers would beg to differ.
But I don't want renames, either, the problem in my mind is that the magic
doesn't work for an explicit routine (only one that is defaulted). This is
about the only case in Ada that I can think of where that is true.
In Adam's example, I almost certainly would have given the parameter explicitly
rather than insert a use clause:
with Pack1;
with Pack2;
package Pack3 is
package Inst is new Pack2.Gen_Pack (Pack1.Root, Pack1.Oper_1);
end Pack3;
which is a lot less work (and maintainance issues) than a renames!
But the equivalent:
with Pack1;
with Pack2;
package Pack4 is
package Inst is new Pack2.Gen_Pack (Pack1.Root'Class, Pack1.Oper_1);
-- ERROR:
end Pack4;
is illegal, because Oper_1 doesn't match. The only choices are to put in a
full use clause (no way) or to define your own Oper_1 (note that I don't see
any value to a rename here, either, the full routine isn't much longer):
with Pack1;
with Pack2;
package Pack4 is
procedure Oper_1 (X : in out Pack1.Root'Class);
package Inst is new Pack2.Gen_Pack (Pack1.Root'Class, Oper_1);
end Pack4;
package body Pack4 is
procedure Oper_1 (X : in out Pack1.Root'Class) is
begin
Pack1.Oper_1 (X);
end Oper_1;
end Pack4;
Obviously, this extra routine adds additional junk to maintain and an additional
source of errors. And it's weird that an implicit subprogram can be built if
no name is given here, but giving an explicit name loses this option.
****************************************************************
From: Tucker Taft
Date: Friday, October 24, 2008 9:00 PM
I would be more sympathetic to making the explicit parameter work than making
the rename work. I would think the adjustment to the wording to make the
explicit parameter work might not be too horrendous.
****************************************************************
From: Steve Baird
Date: Friday, February 4, 2011 1:34 PM
> Summary of action items:
> Steve:
> AI05-0131-1 – Work with Randy and Gary to create a complete
> proposal for this AI.
Here is a proposal for this AI.
Thanks to Randy and Gary for their review of an earlier version and suggested
improvements.
As always, feedback would be appreciated.
-- Steve
--------------
AI05-0071 introduced two distinct problems.
There is the problem that originally motivated the current AI, the inconsistency
between the treatment of defaulted and non-defaulted actual subrogram parameters
for an instance.
There is also the problem of determining what AI05-0071 means (and fixing it if
we don't like the answer) in the case where the instance would have been legal
without AI05-0071, as in
package Pkg1 is
type T1 is tagged null record;
procedure Foo (X1 ; T1);
generic
type T2 (<>) is private;
with procedure Foo (X2 : T2) is <>;
package G is
end G;
end Pkg;
with Pkg1;
package Pkg2 is
use Pkg1;
procedure Foo (X : T1'Class);
package I is new G (T1'Class); -- ambiguous, and thus illegal?
end Pkg2;
Recall that AI05-0071 introduces the following mouthful:
If a subtype_mark in the profile of the formal_subprogram_declaration
denotes a formal private or formal derived type and the actual type
for this formal type is a class-wide type T'Class, then for the
purposes of resolving this default_name at the point of the
instantiation, for each primitive subprogram of T that has a matching
defining name, that is directly visible at the point of the
instantiation, and that has at least one controlling formal parameter,
a corresponding subprogram with the same defining name is directly
visible, but with T systematically replaced by T'Class in the types of
its profile. The body of such a subprogram is as defined in 12.5.1 for
primitive subprograms of a formal type when the actual type is
class-wide.
It is hard to figure out how the visibility rules apply to the above example
because the AI05-0071 wording just says that a "Foo" with the right profile "is
directly visible" without defining the declaration point (and thus, the scope)
of this "Foo".
In any case, it seems unfriendly to reject the above instantiation.
It seems clear what the user wants.
One could imagine an approach using (in effect) a preference rule, where the
implicit declaration is only referenced if it is needed. This would introduce
Beaujolais effects because adding/deleting a use clause could affect the need
for the implicit declaration.
Consider the variation of the above example where we replace
procedure Foo (X : T1'Class);
with
package Foo_Pkg is
procedure Foo (X : T1'Class);
end Foo_Pkg;
use Foo_Pkg;
and then consider the effect of deleting the "use Foo_Pkg;".
This illustrates how a preference rule could introduce Beaujolais effects.
To deal with this aspect of the problem, the proposed wording given below says
that the implicitly declared declaration is not "directly visible", but rather
"potentially use-visible".
This is the only part of the wording changes that is motivated by this
"homograph collision" problem. The rest of the wording changes are motivated by
the problem that this AI was originally created to address.
It seems odd to introduce the possibility of a "potentially use-visible"
declaration in a program which does not contain the reserved word "use"
anywhere, but this seems to solve the problem and no better approach has been
found.
The proposed wording for this AI:
====
!wording
Add after 8.4(8.2)
Certain implicitly declared declarations may become potentially
use-visible in certain contexts as described in 12.6.
Add after 12.6(9):
If a subtype_mark in the profile of the formal_subprogram_declaration
denotes a formal private or formal derived type and the actual type
for this formal type is a class-wide type T'Class, then for the
purposes of resolving the corresponding actual subprogram at the
point of the instantiation, certain implicitly declared declarations
may be available as possible resolutions as follows:
For each primitive subprogram of T that has a matching defining
name, that is directly visible at the point of the instantiation,
and that has at least one controlling formal parameter, a
corresponding implicitly declared subprogram with the same
defining name, and having the same profile as the primitive
subprogram except that T is systematically replaced by T'Class
in the types of its profile, is potentially use-visible.
The body of such a subprogram is as defined in 12.5.1 for primitive
subprograms of a formal type when the actual type is class-wide.
Undo the 12.6(10) wording changes of AI05-0071, leaving only the original (i.e.,
pre-AI05-0071) first sentence:
If a generic unit has a subprogram_default specified by a box, and the
corresponding actual parameter is omitted, then it is equivalent to an
explicit actual parameter that is a usage name identical to the
defining name of the formal.
====
The original example which motivated AI05-0071 remains legal, but now this is
handled via the equivalence stated in the one sentence of 12.6(10) that has
remained constant throughout all of these changes (listed above), as opposed to
via an only-for-defaulted-parameters rule.
Note that this proposal follows AI05-0071 in building implicit declarations only
for primitive subprograms which have at least one controlling formal parameter
(as opposed to only a controlling result), AI05-0071 got this part right -
building on 12.5.1(23.3/2)'s weirdness seems like a bad idea. With at least one
controlling operand, we have a nice clean wrapper model. When we only have a
controlling result, things get messy.
Note that with the proposed changes, the
package Foo_Pkg is
procedure Foo (X : T1'Class);
end Foo_Pkg;
use Foo_Pkg;
version of the example described above will be rejected (which is a good thing -
we don't want Beaujolais effects).
It would also be rejected (for the same reason) if the actual parameter were
specified explicitly via a simple name, as in
package Foo_Pkg is
procedure Foo (X : T1'Class);
end Foo_Pkg;
use Foo_Pkg;
package I is new G (T1'Class, Foo);
but would be accepted if the name of the actual were qualified, as in
package Foo_Pkg is
procedure Foo (X : T1'Class);
end Foo_Pkg;
use Foo_Pkg;
package I is new G (T1'Class, Foo_Pkg.Foo);
(and note that adding/deleting the "use Foo_Pkg;" in this version of the example
would make no difference - that use clause has no effect on this example even if
it is present).
****************************************************************
From: Bob Duff
Date: Friday, February 4, 2011 2:17 PM
> As always, feedback would be appreciated.
Looks good to me.
> given below says that the implicitly declared declaration is
"implicitly declared declaration" --> "implicit declaration"
throughout. Declarations aren't declared, unless you're the DOR Dept. ;-)
****************************************************************
From: Steve Baird
Date: Friday, February 4, 2011 2:33 PM
> "implicitly declared declaration" --> "implicit declaration"
> throughout. Declarations aren't declared, unless you're the DOR Dept.
> ;-)
>
Sounds right.
You probably don't get your cash from an ATM machine either.
****************************************************************
From: Randy Brukardt
Date: Friday, February 4, 2011 11:39 PM
...
> You probably don't get your cash from an ATM machine either.
Gee, I do. :-) I also use the ACATS test suite all of the time. (Indeed, I
usually refer to it that way on comp.lang.ada so the reference is clear to
newbies, even though that's technically redundant.)
BTW, it took me a long time (until just now, even though I first read this
message when it was sent 10 hours ago) to even figure out what you were getting
at.
In any case, I've turned your original message into a proper AI (with the
change), so this item can be crossed off from the open action item list.
****************************************************************
From: Randy Brukardt
Date: Saturday, February 5, 2011 12:04 AM
Wording issue:
...
> Add after 12.6(9):
> If a subtype_mark in the profile of the formal_subprogram_declaration
> denotes a formal private or formal derived type and the actual type
> for this formal type is a class-wide type T'Class, then for the
> purposes of resolving the corresponding actual subprogram at the
> point of the instantiation, certain implicit declarations
> may be available as possible resolutions as follows:
> For each primitive subprogram of T that has a matching defining
> name, that is directly visible at the point of the instantiation,
> and that has at least one controlling formal parameter, a
> corresponding implicitly declared subprogram with the same
> defining name, and having the same profile as the primitive
> subprogram except that T is systematically replaced by T'Class
> in the types of its profile, is potentially use-visible.
> The body of such a subprogram is as defined in 12.5.1 for primitive
> subprograms of a formal type when the actual type is class-wide.
The indented wording starts "For each primitive subprogram of T that has a
matching defining name, ..."
"matching defining name" to what?? The wording never says, at least not clearly.
In AI05-0071-1, it was matching the name of the formal subprogram, but that
isn't right here -- if the name is given explicitly, we want routines that match
that name, not anything else.
I'm not sure it is worth trying to restrict the routines that get created here.
Use visibility seems to eliminate most problems that could occur, and the
limitation to use for "resolving the corresponding actual subprogram" seems to
eliminate the rest. So I would just drop "matching defining name". If we do want
to restrict them, we need a lot more complex wording than "matching defining
name"!
****************************************************************
From: Randy Brukardt
Date: Saturday, February 5, 2011 12:28 AM
Having thought about this some more, it seems wrong to mention the defining name in
any case. There are lots of properties which would prevent a candidate subprogram from
matching a formal subprogram: wrong "kind" (procedure vs. function), wrong name,
wrong number of parameters, wrong type for some parameter or the result, etc. Any
property of a type conformant profile can be used in resolution, along with the
defining name (that's what "expected profile" means). So why hone in on that one
property: they're all equally relevant.
So I dropped the "matching defining name" wording, and added the following AARM note:
AARM Implementation Note:
Although the above wording seems to require constructing implicit
versions of all of the primitive subprograms of type T, it should be
clear that a compiler only need to consider those that could possibly
resolve to the corresponding actual subprogram. For instance, if the formal
subprogram is a procedure with two parameters, and the actual subprogram
name is Bar (either given explicitly or by default), the compiler
need not consider primitives that are functions, that have the wrong
number of parameters, that have defining names other than Bar, and so on;
thus it does not need to construct implicit declarations for those
primitives.
****************************************************************
Questions? Ask the ACAA Technical Agent