Version 1.4 of ais/ai-00310.txt

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

!standard 6.4(08)          03-05-22 AI95-00310/04
!class amendment 02-09-30
!status Amendment 200Y 03-02-19
!status ARG Approved 5-0-3 03-02-08
!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 overload 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 be unambiguous, 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 overload resolution, making the call ambiguous.
!proposal
(See wording.)
!wording
Add "The name or prefix shall not resolve to denote an abstract subprogram unless it is also a dispatching subprogram." to 6.4(8).
Add an AARM note: This rule is talking about dispatching operations (which is a static concept) and not about dispatching calls (which is a dynamic concept).
!discussion
It is a matter of appreciation how much should be "automatic" vs. "explicit" in overload resolution. It can be argued that stating explicitly 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 overload 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 untagged 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 overload 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 overload 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.
The 'obvious' change of making 3.9.3(7) a Name Resolution Rule would be impossible to implement, because it is impossible to tell if a call is a dispatching call until after it has been resolved. In order to do this, 3.9.3(7) would have to be split into a pair of similar rules (one covering dispatching operations and the other covering dispatching calls).
Therefore, it seems simpler to change 6.4(8) to prevent abstract non- dispatching operations from being even considered by the overload 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.
!example
(See problem.)
!corrigendum 6.4(8)
Replace the paragraph:
The name or prefix given in a procedure_call_statement shall resolve to denote a callable entity that is a 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 function. When there is an actual_parameter_part, the prefix can be an implicit_dereference of an access-to-subprogram value.
by:
The name or prefix given in a procedure_call_statement shall resolve to denote a callable entity that is a 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 function. The name or prefix shall not resolve to denote an abstract subprogram unless it is also a dispatching subprogram. When there is an actual_parameter_part, the prefix can be an implicit_dereference of an access-to-subprogram value.
!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

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

From: Randy Brukardt

The minutes of Meeting 18 say to replace the wording by:

"The name or prefix shall not resolve to denote an abstract nondispatching
subprogram."

I didn't use this, because "nondispatching subprogram" is not a term
defined in the Standard.

The Standard indexes "nondispatching call", but there is no definition (or use!)
of the term where it's indexed. And there is no index entry for "nondispatching
operation". So, neither of these concepts is defined. Moreover,
"nondispatching call" is never used in normative text, and "nondispatching
operation" and "nondispatching subprogram" are never used at all.

It's not clear to me that "nondispatching" is automatically the reverse of
"dispatching". This isn't a case of typical English usage, and the lack of a
hyphen makes it look like a different word.

I tried:
The name or prefix shall resolve to denote an non-abstract or dispatching
subprogram.
(which is closer to the original proposed wording), but this uses the
undefined term "non-abstract". This looks a bit more like an inverse, because
of the hyphen. But this too is not defined by the standard, and it is used only
in one RM note, and various AARM annotations.

So I settled on:

The name or prefix shall not resolve to denote an abstract subprogram unless it
is also a dispatching subprogram.

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


Questions? Ask the ACAA Technical Agent