!standard 6.4(08) 03-02-19 AI95-00310/03 !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 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. !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 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. 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 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.) !corrigendum 6.4(8) @drepl The @fa or @fa given in a @fa shall resolve to denote a callable entity that is a procedure, or an entry renamed as (viewed as) a procedure. The @fa or @fa given in a @fa shall resolve to denote a callable entity that is a function. When there is an @fa, the prefix can be an @fa of an access-to-subprogram value. @dby The @fa or @fa given in a @fa shall resolve to denote a callable entity that is a procedure, or an entry renamed as (viewed as) a procedure. The @fa or @fa given in a @fa shall resolve to denote a callable entity that is a function. The @fa or @fa shall not resolve to denote an abstract subprogram unless it is also a dispatching subprogram. When there is an @fa, the prefix can be an @fa 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. ****************************************************************