!standard 4.1.3(9.2/2) 08-10-21 AI05-0090-1/05 !standard 9.1(9.5/2) !standard 9.4(11.4/2) !class binding interpretation 08-02-04 !status ARG Approved 7-0-1 08-06-21 !status work item 08-02-04 !status received 08-02-04 !priority Medium !difficulty Medium !qualifier Omission !subject Ambiguities with prefixed views of synchronized primitives !summary A selected_component whose selector_name can denote the (implicit) overriding of an inherited primitive of a synchronized type, as well as denote an entry (or protected subprogram) that implements the inherited primitive, is not ambiguous, and resolves to the entry (or protected subprogram). !question Consider the following example: package Synch_Pkg is type Synch_Interface is synchronized interface; procedure Prim (S : in out Synch_Interface) is abstract; procedure Yet_Another_Op (Obj : in out Synch_Interface'Class); end Synch_Pkg; with Synch_Pkg; package Task_Pkg is task type Task_Type is new Synch_Pkg.Synch_Interface with entry Prim; entry Other_Prim; entry Yet_Another_Op; end Task_Type; -- Implicit declaration of overriding primitive (as per AI05-0042): -- procedure Prim (S : in out Task_Type); procedure Other_Prim (Tsk : in out Task_Type); -- Legal? (No.) end Task_Pkg; ... T : Task_Pkg.Task_Type; ... T.Prim; -- (1) Ambiguous? (No.) T.Other_Prim; -- (2) Ambiguous? (Not possible; the procedure declaration is illegal.) T.Yet_Another_Op; -- (3) Ambiguous? (Yes.) The call to Prim at (1) using prefixed notation appears to be ambiguous, because the selector could denote either the entry Prim, or the implicitly declared overriding of the primitive inherited from Synch_Interface (which is defined to exist by AI05-0042). This is clearly not what is intended. Also, there can be subprograms with prefixed views that are homographic with an entry of the task type, such as procedures Yet_Another_Op and Other_Prim, but that means that calls using prefixed notation are ambiguous, because the call could be either to the subprogram or to the entry (by 4.1.3 paragraphs 8-9.2/2). Are such calls intended to be ambiguous? (Yes, but in the case of Other_Prim the procedure declaration is already illegal by 9.1(9.5/2).) !recommendation (See summary.) !wording Modify 4.1.3(9.2/2): The prefix (after any implicit dereference) shall resolve to denote an object or value of a specific tagged type T or class-wide type T'Class. The selector_name shall resolve to denote a view of a subprogram declared immediately within the declarative region in which an ancestor of the type T is declared. The first formal parameter of the subprogram shall be of type T, or a class-wide type that covers T, or an access parameter designating one of these types. The designator of the subprogram shall not be the same as that of a component of the tagged type visible at the point of the selected_component. {The subprogram shall not be an implicitly declared primitive operation of type T that overrides an inherited subprogram implemented by an entry or protected subprogram visible at the point of the selected_component.} The selected_component denotes a view of this subprogram that omits the first formal parameter. This view is called a prefixed view of the subprogram, and the prefix of the selected_component (after any implicit dereference) is called the prefix of the prefixed view. Add this AARM paragraph following 4.1.3(9.2/2): AARM Discussion: The part of the rule that excludes a primitive overriding subprogram as a selector applies only to the wrapper subprogram that is implicitly declared to override a subprogram inherited from a synchronized interface that is implemented by an operation of a task or protected type (see 9.1 and 9.4, as amended by AI05-0042). We don't want calls that use a prefixed view to be ambiguous between the wrapper subprogram and the implementing entry or protected operation. Note that it is illegal to declare an explicit primitive that has a prefixed view that is homographic with one of the type's operations, so in normal cases it isn't possible to have an ambiguity in a prefix call. However, a class-wide operation of an ancestor type and declared in the same declarative list with the ancestor type is also considered, and that can make a call ambiguous. Replace 9.1(9.5/2): The prefixed view profile of an explicitly declared primitive subprogram of a tagged task type shall not be type conformant with any entry of the task type, if the first parameter of the subprogram is of the task type or is an access parameter designating the task type. with the following: The prefixed view profile of an explicitly declared primitive subprogram of a tagged task type shall not be type conformant with any entry of the task type, if the subprogram has the same defining name as the entry and the first parameter of the subprogram is of the task type or is an access parameter designating the task type. Replace 9.4(11.4/2): The prefixed view profile of an explicitly declared primitive subprogram of a tagged protected type shall not be type conformant with any protected operation of the protected type, if the first parameter of the subprogram is of the protected type or is an access parameter designating the protected type. with the following: The prefixed view profile of an explicitly declared primitive subprogram of a tagged protected type shall not be type conformant with any protected operation of the protected type, if the subprogram has the same defining name as the protected operation and the first parameter of the subprogram is of the protected type or is an access parameter designating the protected type. !discussion The ambiguity in the case of the call at (3), to a synchronized operation declared within a task or protected type that is a homograph of the prefixed view of an explicit subprogram, seems unavoidable. We could try to define some preference rule similar to that for components that conflict with prefixed views, as is done in 4.1.3(9.2/2), to select the entry or protected subprogram interpretation, but that seems like a bad idea. For one thing, unlike a component, such operations are overloadable. Giving preference to either the synchronized operation or the subprogram with the homographic prefixed view could too easily result in confusion as to which operation was being named. So it's best to leave the language as it is, with the potential for ambiguities, but ones that can generally be avoided either using normal function call notation rather than a prefixed view, or by using named notation on calls. In the case of the entry and primitive procedure named Other_Prim in the question, the declaration of the procedure is illegal by the existing rule in 9.1(9.5/2), which disallows explicitly declared primitives that have a prefixed view that is homographic with an entry of the type. (A similar rule exists in 9.4(11.4/2), covering the case of protected operations.) One oversight that was noted, though, is that those rules need to mention that the conflicting subprogram must have the same defining_identifier (or defining_operator_symbol) as the entry or protected subprogram, so those paragraphs are corrected to reflect that. The first part of the question (concerning the call at (1)) involves a similar case, but one where we definitely want the call to resolve to one of the two available interpretations, since otherwise prefixed notation can't be used at all. The situation is that of a subprogram inherited from a synchronized interface that is implemented by an entry or protected subprogram. By AI05-0042, such an inherited subprogram is defined to be overridden by an implicit subprogram (see the last sentence of 9.1(9.2/3) and 9.4(11.1/3)). The problem is that a call to the implicitly declared overriding subprogram using prefixed notation will be ambiguous, because it could also be interpreted as a call to the entry or protected subprogram that implements the inherited subprogram. In some cases such a call could be disambiguated using named associations for the parameters, but having to use parameter names to disambiguate calls would only be an ugly workaround and an inconvenience for users. Clearly a change is needed to effectively give preference to one of the two operations. This is accomplished by choosing the entry (or protected subprogram), and removing the interpretation of the implicit overriding operation at the point of a selector_name in a prefixed context. It makes sense to resolve in favor of the entry or protected subprogram over the implicit overriding subprogram since the former is explicit in the program text. This preference rule, added to 4.1.3(9.2/2), is similar to the rule in that same paragraph for preferring a component over a subprogram with the same name. !corrigendum 4.1.3(9.2/2) @drepl @xindent (after any implicit dereference) shall resolve to denote an object or value of a specific tagged type @i or class-wide type @i'Class. The @fa shall resolve to denote a view of a subprogram declared immediately within the declarative region in which an ancestor of the type @i is declared. The first formal parameter of the subprogram shall be of type @i, or a class-wide type that covers @i, or an access parameter designating one of these types. The designator of the subprogram shall not be the same as that of a component of the tagged type visible at the point of the @fa. The @fa denotes a view of this subprogram that omits the first formal parameter. This view is called a @i of the subprogram, and the @fa of the @fa (after any implicit dereference) is called the @i of the prefixed view.> @dby @xindent (after any implicit dereference) shall resolve to denote an object or value of a specific tagged type @i or class-wide type @i'Class. The @fa shall resolve to denote a view of a subprogram declared immediately within the declarative region in which an ancestor of the type @i is declared. The first formal parameter of the subprogram shall be of type @i, or a class-wide type that covers @i, or an access parameter designating one of these types. The designator of the subprogram shall not be the same as that of a component of the tagged type visible at the point of the @fa. The subprogram shall not be an implicitly declared primitive operation of type @i that overrides an inherited subprogram implemented by an entry or protected subprogram visible at the point of the @fa. The @fa denotes a view of this subprogram that omits the first formal parameter. This view is called a @i of the subprogram, and the @fa of the @fa (after any implicit dereference) is called the @i of the prefixed view.> !corrigendum 9.1(9.5/2) @drepl The prefixed view profile of an explicitly declared primitive subprogram of a tagged task type shall not be type conformant with any entry of the task type, if the first parameter of the subprogram is of the task type or is an access parameter designating the task type. @dby The prefixed view profile of an explicitly declared primitive subprogram of a tagged task type shall not be type conformant with any entry of the task type, if the subprogram has the same defining name as the entry and the first parameter of the subprogram is of the task type or is an access parameter designating the task type. !corrigendum 9.4(11.4/2) @drepl The prefixed view profile of an explicitly declared primitive subprogram of a tagged protected type shall not be type conformant with any protected operation of the protected type, if the first parameter of the subprogram is of the protected type or is an access parameter designating the protected type. @dby The prefixed view profile of an explicitly declared primitive subprogram of a tagged protected type shall not be type conformant with any protected operation of the protected type, if the subprogram has the same defining name as the protected operation and the first parameter of the subprogram is of the protected type or is an access parameter designating the protected type. !ACATS Test An ACATS C-Test should be written to check that the legal case in the example actually works. !appendix From: Gary Dismukes Sent: Monday, February 4, 2008 4:44 PM This is the draft version of the AI assigned to me in Fairfax (not assigned a number yet) [this is version /01 of the AI - ED], which arose out of a question raised during the meeting. There are actually two similar questions involving apparent ambiguities that can occur with prefixed calls to operations of synchronized tagged types considered here. One of these cases of ambiguities doesn't seem to require any action, but the other one needs correction. The most appropriate way to fix this can be discussed at the upcoming meeting (assuming this makes it to the agenda), but in this draft I've suggested one way that it could be fixed. **************************************************************** From: Edmond Schonberg Sent: Monday, February 4, 2008 5:08 PM ... > procedure Prim (S : Synch_Interface) is abstract; S should be an in-out parameter if it is to be implemented by an entry. ... > The call to Prim at (1) using prefixed notation appears to be ambiguous, > because the selector could denote either the entry Prim, or the implicitly > declared overriding of the primitive inherited from Synch_Interface (which > is defined to exist by AI05-0042). This is clearly not what is > intended. Agreed. > [Or perhaps the preference should really be the other way around, with > the implicit overriding subprogram being hidden from all visibility in > a prefixed call: > > The declaration of an entry or protected subprogram that implements > a subprogram inherited from a progenitor type is hidden from all > visibility at the point of a selector_name in a selected_component > whose prefix resolves to an object or value of the parent task or > protected type. But in that case the second declaration would be legal, because the implicit subprogram would then be overridden by the explicit one according to the usual rules, and there would be no ambiguity. So the first choice is preferable (if we want (2) to remain ambiguous. **************************************************************** From: Gary Dismukes Sent: Monday, February 4, 2008 6:35 PM ... > > procedure Prim (S : Synch_Interface) is abstract; > > S should be an in-out parameter if it is to be implemented by an entry. You're right, thanks for catching. I missed going back and making that correction despite having corrected my compilation test of the same code. [Correction made in version /01 of the AI - ED.] > > [Or perhaps the preference should really be the other way around, with > > the implicit overriding subprogram being hidden from all visibility in > > a prefixed call: (Aargh, I just noticed that I failed to adjust the wording on the above paragraph after swapping some of my text around. That one should read "with the entry or protected subprogram being hidden...". That's what I get for running out of time and not doing a final careful review of what I'd written. At least the next paragraph reflected what I meant in the above.) [Correction made in version /01 of the AI - ED.] > > The declaration of an entry or protected subprogram that implements > > a subprogram inherited from a progenitor type is hidden from all > > visibility at the point of a selector_name in a selected_component > > whose prefix resolves to an object or value of the parent task or > > protected type. > > But in that case the second declaration would be legal, because the (I assume you mean "the second call" above rather than "second declaration".) > implicit subprogram would then be overridden by the explicit one > according to the usual rules, and there would be no ambiguity. So > the first choice is preferable (if we want (2) to remain ambiguous. I think there are two confusions here. First, the operation Other_Prim called at (2) isn't a case of overriding, as there's no inherited operation by that name in my example. But second, if it were an overriding, then there would be a violation of the rules in 9.1(9.6/2-9.8/2), which disallow having both an overriding primitive and an "implemented by" operation for an inherited subprogram. So I don't think that the fix for (1) would have any effect on the ambiguity of cases such as (2). **************************************************************** From: Edmond Schonberg Sent: Monday, February 4, 2008 10:27 PM > I think there are two confusions here. First, the operation > Other_Prim called at (2) isn't a case of overriding, as there's > no inherited operation by that name in my example. But second, > if it were an overriding, then there would be a violation of the > rules in 9.1(9.6/2-9.8/2), which disallow having both an overriding > primitive and an "implemented by" operation for an inherited > subprogram. > So I don't think that the fix for (1) would have any effect on the > ambiguity of cases such as (2). Right, I misread the example, Other_Prim is well-named, and this is definitely (unambiguously?) ambiguous. **************************************************************** From: Tucker Taft Sent: Monday, February 4, 2008 8:21 PM > ... Another possibility would be > to try and make certain declarations illegal, but that isn't appealing > either, as it would introduce illegalities for no particularly good > reason (plus it wouldn't work for technical reasons). Can you explain these "technical reasons"? We make it illegal to have an entry or protected subprogram that is homographic with the prefixed view of an explicit overriding. Why can't we do the same for an explicit non-overriding primitive? > ... > Clearly we need a change to effectively give preference to one of the > two operations. The proposed fix is to prefer the interpretation of > the entry (or protected subprogram), and to consider the implicit > overriding operation effectively hidden at the point of a selector_name > in a prefixed call. It seems somewhat more natural to prefer the entry > or protected subprogram over the implicit overriding subprogram since > the former is explicit in the program text (though formulating the > rule the other way could also be considered). I strongly prefer having the entry/protected subprogram visible via prefixed notation. That is what you would get in the absence of the interface, and it seems weird that the formal parameter names, defaults, etc., would suddenly change just because the task type now implements an interface. So I agree with your current solution, and would be opposed to the alternate you considered. > ...In terms of the rules, > this is implemented by making the implicit overriding subprogram > hidden from all visibility at the point of a selector_name in cases > that would otherwise be ambiguous. Do we really need to use the term "hidden from all visibility" here? I think all you need to do is have 4.1.3(9.2) include a rule that disallows referring to a primitive subprogram that is implemented by an entry or protected subprogram. Remember that 4.1.3(9.2) is all considered "Name Resolution" so disallowing something is effectively creating an overload resolution rule. Hiding from "all visibility" seems like overkill, and is probably exactly what we *don't* want to do, since we still want to be able to refer to the implicitly declared primitive using "unprefixed" subprogram call notation. **************************************************************** From: Gary Dismukes Sent: Wednesday, February 6, 2008 12:11 AM > > ... Another possibility would be > > to try and make certain declarations illegal, but that isn't appealing > > either, as it would introduce illegalities for no particularly good > > reason (plus it wouldn't work for technical reasons). > > Can you explain these "technical reasons"? > We make it illegal to have an entry or > protected subprogram that is homographic with the > prefixed view of an explicit overriding. Why can't we > do the same for an explicit non-overriding primitive? You're right, we could do that, and I started off by doing that. But there are other cases that that have a similar effect where the conflicting operation isn't a primitive, such as a class-wide subprogram: procedure Other_Op (T : Synchronized_Type'Class); Do we want to disallow that as well? I'm assuming we don't. At the time it had seemed to me to be needlessly restrictive to disallow any of these cases. After all, an implementation can always issue a warning that certain calls will be ambiguous. I was thinking that a user might have some reason to declare an explicit primitive that conflicts, and have a homographic task entry that can still be called from within the task even though it can't be called from outside. But I'm willing to admit that that's a stretch, and probably isn't an idiom worth allowing, so if the feeling is that such a conflict with a primitive almost certainly indicates an error, then I'm willing to go along with making the case of a primitive illegal. > > ... > > Clearly we need a change to effectively give preference to one of the > > two operations. The proposed fix is to prefer the interpretation of > > the entry (or protected subprogram), and to consider the implicit > > overriding operation effectively hidden at the point of a selector_name > > in a prefixed call. It seems somewhat more natural to prefer the entry > > or protected subprogram over the implicit overriding subprogram since > > the former is explicit in the program text (though formulating the > > rule the other way could also be considered). > > I strongly prefer having the entry/protected subprogram > visible via prefixed notation. That is what you would > get in the absence of the interface, and it seems weird > that the formal parameter names, defaults, etc., would > suddenly change just because the task type now implements > an interface. So I agree with your current solution, and > would be opposed to the alternate you considered. For some reason I was wavering, then ended up leaning towards the recommended preference, but hadn't fully solidified my commitment to that by the time I wrote it up, even though it felt right to me based on wanting to get the formal names, etc. However, your comment has convinced me that it's the right choice. :-) (I was a little worried that some semantic glitch might pop up that would argue for preferring the other, so wanted to leave the option open, but that was most probably just FUD.) > > ...In terms of the rules, > > this is implemented by making the implicit overriding subprogram > > hidden from all visibility at the point of a selector_name in cases > > that would otherwise be ambiguous. > > Do we really need to use the term "hidden from all visibility" here? > I think all you need to do is have 4.1.3(9.2) include a rule > that disallows referring to a primitive subprogram that is implemented > by an entry or protected subprogram. Remember that 4.1.3(9.2) > is all considered "Name Resolution" so disallowing something is > effectively creating an overload resolution rule. Hiding from > "all visibility" seems like overkill, and is probably exactly > what we *don't* want to do, since we still want to be able to > refer to the implicitly declared primitive using "unprefixed" > subprogram call notation. I defined it as hidden only within the specific context of the selector_name, not hidden in general, so that it wouldn't affect normal calls, but I admit that's an unorthodox use of "hidden from all visibility". Anyway, I'm fine with going the route of adding a rule in 4.1.3 to cover this case, and will try to draft some wording for that. BTW, in your comment above you say "disallows referring to a primitive subprogram that is implemented by...", but is that really what we want? That would disallow dispatching calls using prefixed notation. My attempted fix was written to hide the implicit primitive only for calls that could be made to the entry itself, that is, calls where the prefix is of the specific type, but to still allow calls with class-wide prefixes to be made to the dispatching primitive. It seems unfortunate to lose the dispatching calls, though obviously those would still be possible using normal call notation, just not with prefixed notation. Wouldn't it be preferable to try and formulate the rule so as to also permit dispatching calls that use prefixed notation? **************************************************************** From: Tucker Taft Sent: Wednesday, February 6, 2008 5:30 AM > You're right, we could do that, and I started off by doing that. > But there are other cases that that have a similar effect where > the conflicting operation isn't a primitive, such as a class-wide > subprogram: > > procedure Other_Op (T : Synchronized_Type'Class); > > Do we want to disallow that as well? I'm assuming we don't. These aren't homographs, so trying to make these illegal is probably more trouble than it is worth. But that doesn't change my mind about disallowing the more common case of a homographic primitive. > > At the time it had seemed to me to be needlessly restrictive to > disallow any of these cases. After all, an implementation can > always issue a warning that certain calls will be ambiguous. I was > thinking that a user might have some reason to declare an explicit > primitive that conflicts, and have a homographic task entry that > can still be called from within the task even though it can't be > called from outside. But I'm willing to admit that that's a stretch, > and probably isn't an idiom worth allowing, so if the feeling is that > such a conflict with a primitive almost certainly indicates an error, > then I'm willing to go along with making the case of a primitive > illegal. I think that is more consistent. It seems weird that it is illegal to have a homographic overriding, but it is OK to have a homographic operation that does *not* override an inherited primitive. I don't think you are doing the user any favors by having this distinction. ... > It seems unfortunate to lose the dispatching calls, though obviously > those would still be possible using normal call notation, just not with > prefixed notation. Wouldn't it be preferable to try and formulate the > rule so as to also permit dispatching calls that use prefixed notation? I'm not sure what you mean by "losing" dispatching calls. If I have a prefix of task_type'Class, I still have visibility on the entries of task_type I hope! I just don't need to do any "dispatching" to know which entry, since you can't further extend a concrete task type. This is similar to referring to the components of a record type when the prefix of the selected component is record_type'Class. No dispatching is involved. **************************************************************** From: Randy Brukardt Sent: Saturday, June 14, 2008 12:37 AM ... > Consider the following example: > > package Synch_Pkg is > > type Synch_Interface is synchronized interface; > > procedure Prim (S : in out Synch_Interface) is abstract; > > task type Task_Type is new Synch_Interface with > entry Prim; > entry Other_Prim; > entry Yet_Another_Op; > end Task_Type; > > package Nested_Pkg is > procedure Yet_Another_Op (Tsk : in out Task_Type); > end Nested_Pkg; > > -- Implicit declaration of overriding primitive (as per AI05-0042): > -- procedure Prim (S : in out Task_Type); > > procedure Other_Prim (Tsk : in out Task_Type); -- Legal? (No.) > > end Synch_Pkg; > > ... > > T : Synch_Pkg.Task_Type; > > use Synch_Pkg.Nested_Pkg; ... > T.Yet_Another_Op; -- (2) Ambiguous? (Yes.) I don't understand this. The operation in the nested package is not eligible to be a prefixed view, as it is not "declared *immediately* within the declarative region where an ancestor of T is declared". The use clause is irrelevant, that would make the operation directly visible as: Yet_Another_Op(T); but that's not relevant here. I think the only way to get an ambiguity is with a class-wide operation (which could be in an ancestor package): procedure Yet_Another_Op (Obj : in out Synch_Interface'Class); Thus, the example of the question would probably be better written as: package Synch_Pkg is type Synch_Interface is synchronized interface; procedure Prim (S : in out Synch_Interface) is abstract; procedure Yet_Another_Op (Obj : in out Synch_Inteface'Class); end Synch_Pkg; with Synch_Pkg; package Task_Pkg is task type Task_Type is new Synch_Pkg.Synch_Interface with entry Prim; entry Other_Prim; entry Yet_Another_Op; end Task_Type; -- Implicit declaration of overriding primitive (as per AI05-0042): -- procedure Prim (S : in out Task_Type); procedure Other_Prim (Tsk : in out Task_Type); -- Legal? (No.) end Task_Pkg; ... T : Task_Pkg.Task_Type; ... T.Prim; -- (1) Ambiguous? (No.) T.Other_Prim; -- (2) Ambiguous? (Not possible; the procedure declaration is illegal.) T.Yet_Another_Op; -- (2) Ambiguous? (Yes.) **************************************************************** From: Gary Dismukes Sent: Saturday, June 14, 2008 12:57 AM > I don't understand this. The operation in the nested package is not > eligible to be a prefixed view, as it is not "declared *immediately* > within the declarative region where an ancestor of T is declared". The > use clause is irrelevant, that would make the operation directly visible as: > Yet_Another_Op(T); > but that's not relevant here. You're right. I was trying to illustrate a case like 2) that showed a legitimate ambiguity (since the call 2) turned out to be wrong because of the illegality) and ended up screwing up the example. > I think the only way to get an ambiguity is with a class-wide > operation (which could be in an ancestor package): Yes, that would make more sense. Thanks for the correction. I guess I'll be seeing this AI again... **************************************************************** From: Randy Brukardt Sent: Saturday, June 14, 2008 1:57 AM ... > > I think the only way to get an ambiguity is with a class-wide > > operation (which could be in an ancestor package): > > Yes, that would make more sense. Thanks for the correction. I just noticed that there are two calls labeled (2) in the question. I'll replace the example with mine and fix the calls to have different numbers. > I guess I'll be seeing this AI again... Maybe, but not for this reason. But I'm didn't check the discussion very closely. ****************************************************************