Version 1.2 of ais/ai-00310.txt

Unformatted version of ais/ai-00310.txt version 1.2
Other versions for file ais/ai-00310.txt

!standard 3.9.3 (07)          03-01-08 AI95-00310/02
!class amendment 02-09-30
!status work item 02-09-30
!status received 02-09-04
!priority Medium
!difficulty Medium
!subject Ignore abstract nondispatching subprograms during overloading
resolution
!summary
A mechanism is added to make it possible to "undefine" an inherited operation for an untagged type. The name resolution rules are changed so that abstract nondispatching subprograms are not considered for overloading resolution. Declaring an abstract override for an inherited operation effectively has the effect of "undefining" this operation.
!problem
Consider the following program:
with Text_Io; use Text_Io; procedure Proc is package Pack is type Unit is new Float; function "*" (L, R : Unit) return Unit is abstract; -- (1) function Image (L : Unit) return String;
type Unit_Squared is new Unit; function "*" (L, R : Unit) return Unit_Squared; -- (2) end Pack;
package body Pack is ... end Pack;
use Pack;
X : Unit := 1.0; begin Put_Line (Image (X * X)); -- (3) Ambiguous end Proc;
Such structures are frequent in programs that take advantage of Ada's strong typing for enforcing physical units consistency. The intent of making the "*" at (1) abstract is, in effect, to remove the (physically incorrect) operation.
To the human reader, the call at (3) appears to not be ambiguous, since the intent is to have only the "*" at (2) available for the type Unit. However, the declaration at (1) is considered a possible interpretation for overloading resolution, making the call ambiguous.
Making 3.9.3(7) an overloading rule would provide the expected behavior. There is no upward compatibility issue, since it would simply make illegal Ada95 programs legal.
!proposal
(See wording.)
!wording
Modify 6.4(8) as follows:
The name or prefix given in a procedure_call_statement shall resolve to denote a callable entity that is a {nonabstract or dispatching} procedure, or an entry renamed as (viewed as) a procedure. The name or prefix given in a function_call shall resolve to denote a callable entity that is a {nonabstract or dispatching} function.
!discussion
It is a matter of appreciation how much should be "automatic" vs. "explicit" in overloading resolution. It can be argued that stating explicitely which subprogram to call is always safer than letting the compiler decide. However, this line of reasoning would lead to concluding that there should be no automatic overloading resolution at all.
In practice, risk should be balanced with usefulness. As a rule of thumb, "automatic" resolution can be allowed if there is no uncertainty about the programmer's intent.
In the case of an abstract subprogram (at least for the non-tagged case), the intent of the programmer is clearly to make the subprogram "unavailable". It seems odd to force the programmer to state: "don't worry, the subprogram that I want to call is not the one that does not exist!".
The only case where the current rule would catch an error and not the new one is if: 1) The programmer incorrectly wants to call an abstract subprogram, and 2) There is another interpretation that happens to be there, but not the one the programmer wants.
This case seems much less likely than other cases where the "wrong" subprogram is called, like for example, having two packages declaring subprograms with the same profile, and having a use clause for the wrong package.
While there is some sympathy for using abstract subprograms as a mechanism for "undefining" an inherited operation, we must be cautious to avoid adding more complexity to the existing overloading resolution rules.
Clearly, abstract subprograms must remain candidates for overload resolution if there is a possibility that they be dispatching operations, so we must not make any change for tagged types. The new rule has to cause abstract subprograms which are not dispatching operations to be ignored by the overloading resolution algorithm.
The case of private types deserves special consideration. It is possible for an untagged private type to have a tagged completion. If such a type could have an abstract primitive subprogram, the new rule would violate the privacy of private types. But in order to have an abstract primitive operation, the full view would have to be abstract, and that's illegal if the partial view is untagged.
Rather than making 3.9.3(7) a Name Resolution Rule (as suggested by the !question), it seems simpler to change 6.4(8) to prevent abstract non- dispatching operations from being even considered by the overloading resolution algorithm. This is likely to have a relatively limited impact on implementations, as such subprograms can be filtered out early in the resolution process (typically at the time when functions or procedures are filtered out to implement the rest of 6.4(8)).
Note that 3.9.3(7) is still useful to reject non-dispatching calls to abstract dispatching operations.
!examples
(See problem.)
!ACATS Test
A C-Test similar to the example should be created.
!appendix

From: Jean-Pierre Rosen
Sent: Wednesday, September 4, 2002  3:15 AM

Before proposing an amendment AI, I think it is wise to ask for the advice
of those who have their feet deep into overloading resolution.... Consider:

with Text_Io; use Text_Io;
procedure Proc is
   package Pack is
      type Unit is new Float;
      function "*" (L, R : Unit) return Unit is abstract; -- (1)
      function Image (L : Unit) return String;

      type Unit_Squared is new Unit;
      function "*" (L, R : Unit) return Unit_Squared; -- (2)
   end Pack;

   package body Pack is
      ...
   end Pack;

   use Pack;

   X : T;
begin
   Put_Line (Image (X * X)); -- (3) Ambiguous
end Proc;

The call at (3) is ambiguous, because although calling (1) is forbidden by
3.9.3(7), it is not an overloading rule, and therefore the compiler cannot
decide between (1) and (2).

Making 3.9.3(7) an overloading rule would be more user friendly, and
(apparently to me) quite doable, since it would simply require to remove
calls to abstract functions from the list of possible interpretations. There
is no upward compatibility problem, since it would simply make illegal Ada95
programs legal.

Any hidden trap I didn't see with this ?

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

From: Robert Dewar
Sent: Wednesday, September 4, 2002  2:19 PM

There is no hidden trap as far as I can see, but I think this is an undesirale
change, I don't understand the motivation for it. Sure there are lots of cases
where you can relax the overloading rules a bit and allow more cases, but
absent a specific compelling example of why this is worth the effort in
a particular case, I see no point in the change.

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

From: Jean-Pierre Rosen
Sent: Wednesday, September 4, 2002  2:54 PM

It is at least surprising from a user point of view that an illegal call is
considered a possible interpretation for the purpose of overloading
resolution, and the example is quite natural for people trying to use the
strict typing of Ada for ensuring dimensions consistency. I discovered this
in a big, industrial project.

Note that for Ada9X, a requirement was to suppress unnecessary "surprising"
rules (like for I in -1..10 loop).
This may not be a requirement for Ada0Y, however if it can be done with
reasonable cost, it may be worth doing.

Whether the cost is "reasonable" is precisely what I am asking here.

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

From: Robert Dewar
Sent: Wednesday, September 4, 2002  3:04 PM

I don't agree that this is surprising behavior. In general there is no
rule that says that illegal calls cannot be considered, and there are
lots of cases in the language where the overloading considers things that
would be illegal if they were resolved (e.g. in the case of aggregates and
conversion operators).

I prefer more ambiguity to less, since I think it is less error prone.
The point of making something abstract is to prevent it being called
accidentally. To me, extending the scope of such accident prevention
to calls that are overloaded seems desirable.

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

From: Robert Duff
Sent: Wednesday, September 4, 2002  3:07 PM

> Any hidden trap I didn't see with this ?

I don't think so.

Something along these lines was proposed during the 9X design.
I don't remember the details.  It was thought at the time to
be complicated.  I don't remember why.  Perhaps the UI teams had
trouble implementing it.

As to whether it's a desirable change or not:  I don't feel strongly,
but I tend to agree with Robert.  A good language design principle
is, "Don't make the resolution rules too smart."

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

From: Robert Duff
Sent: Wednesday, September 4, 2002  3:15 PM

> It is at least surprising from a user point of view that an illegal call is
> considered a possible interpretation for the purpose of overloading
> resolution,...

But this is true for *all* legality rules.  For example, if you have two
procedures with identical specs, except one has an 'in' parameter where
the other has 'in out', then some calls will be ambiguous, even though
one of the possible interpretations is clearly illegal.  This fact might
surprise users, but I'd rather have users surprised at compile time than
at run time.

Now the example you gave is not quite as bad as the 'in' vs. 'in out'
example.  But as a general principle, we *want* ambiguities -- that's
why there exist rules that are legality rules and not resolution rules.

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

From: Adam Beneschan
Sent: Wednesday, September 4, 2002  4:11 PM

> Any hidden trap I didn't see with this ?

In my (very) humble opinion, I think the bug here is that the program
declares a function to be abstract when what is really desired is to
declare the function at (1) to be wiped out of existence.  I really
don't know anything about the history here---was there some reason why
a syntax to "undefine" an inherited subprogram (other than an
operation on a type extension) was considered a bad idea?

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

From: Robert Dewar
Sent: Wednesday, September 4, 2002  8:24 PM

Yes, it would have been reduduant and an extra complexification, when abstract
does exactly that.

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

From: Adam Beneschan
Sent: Thursday, September 5, 2002  10:24 AM

Based on Jean-Pierre's example, I'd have to say that abstract does
*not* do *exactly* that.  It does *almost* that.  But if "abstract"
really did "exactly" wipe a function out of existence, how could a
non-existent function be considered to be a possible interpretation of
a function call?  Seems like a contradiction to me.

If the intent is for "abstract" to serve the additional purpose of
undefining a function, then it looks to me like Jean-Pierre's proposal
is necessary in order for it to serve this purpose completely, instead
of just 90% or so.

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

From: Robert Dewar
Sent: Thursday, September 5, 2002  10:58 AM

On the contrary, the purpose of abstract is to make it clear that calling the
function is improper. I consider the current behavior preferable, which is
that you can't call the function, but it is still there for overloading
purposes, helping to catch cases where you might be trying to call the
function (that you should not be able to call) and in fact would not otherwise
be informed, because you accidentally match some other available function (e.g.
one from an outer scope that you don't even know about).

In my view

a) the behavior of abstract is exactly right and desirable

b) JPR's proposal would damage the safety of the language

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


Questions? Ask the ACAA Technical Agent