AI22-0149-1
!standard 4.1.3(9.2/7) 26-03-26 AI22-0149-1/04
!class Binding Interpretation 25-12-05
!status Revision-202Y 25-12-16
!status ARG Approved 10-0-0 25-12-11
!status work item 25-12-05
!status received 25-12-04
!assigned author Randy Brukardt
!submitter Randy Brukardt
!priority Medium
!difficulty Medium
!qualifier Error
!subject Resolution and ambiguity for prefixed views
Prefixed views are available for access types that complete a private type.
package P1 is
type Priv is private;
function Foo (A : Priv; B : Natural) return Natural;
C : constant Priv;
procedure Sink (Val : in Natural := C.Foo(1)); -- (A)
private
type Priv is access all Natural;
C : constant Priv := new Natural'(1);
Use_It : constant Natural := C.Foo(1); -- (B)
end P1;
AI22-0091-1 says that the prefixed view at (A) is legal, but that the prefixed view at (B) is illegal. The discussion of that AI says:
To avoid the access-type incompatibility, we opt to retain the current required implicit dereference in the case where the prefix is of an access type.
The problem is that there is no such “required implicit dereference” in the wording in the RM. The closest that we come is at the beginning of 4.1.3(9.2/7):
The prefix (after any implicit dereference) shall resolve...
The parenthetical remark here seems to merely note the possibility of an implicit dereference, not to require one.
As such, it appears that the prefixed view at (B) would resolve the same as the one at (A). However, the discussion in AI22-0091-1 is based on a different interpretation of this parenthetical remark.
[Aside: In the examples in this AI, we are using default expressions to show interesting expressions in the public part of the package specification, as these avoid call-before-body-elaboration errors. But these same expressions could occur in client code with the same meaning; and such uses are more interesting in practical cases. Similarly, we are showing expressions in the private part of a package; the same expressions would have the same meaning in the body of the package, which is more likely in practice. ]
Disallow prefixed views for prefixes that have access types, unless the access type completes a private type.
Modify 4.1.3(9.2/7): (As modified by AI22-0091-1)
The prefix ({with or without an}[after any] implicit dereference) shall resolve to denote an object or value of a specific {non-access} type T{, an access type T that completes a private type,} or {a }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, if T is of a tagged type, 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 type T 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 ({with or without an}[after any] implicit dereference) is called the prefix of the prefixed view. In the special case where the prefix is class-wide and controlling, the prefixed view is not abstract, even if the unprefixed view is abstract; otherwise the prefixed view is abstract if and only if the unprefixed view is abstract.
Modify AARM 4.1.3(9.c/7): [from AI22-0091-1]
{In the special case where an access type completes a private type, we do allow a prefixed view of an access type without an implicit dereference. This prevents an anomaly where prefixed notation that is allowed for the private type is not allowed where the full type is visible. That anomaly can cause Beaujolais-like effects as well as surprisingly illegal calls, so it cannot be tolerated. This special case does mean that for the full access type associated with a private type, it is necessary to try the resolution of the prefixed view both with and without an implicit dereference; as usual, if more than one solution is possible, the prefixed view is illegal.}[The rules do cause an anomaly for a private type completed by an access type. In places where only the private type is visible, prefixed notation can be used, while in places where the full type is visible, it cannot be used. As private types completed directly by an access type are uncommon, we're willing to tolerate this anomaly.]
Several ARG members have mis-remembered the resolution rules for selected_components, thinking that there was a preference for an implicit conversion of the prefix. But it seems that there is no such preference for selected components (as well as indexed components).
As noted above, there is only a parenthetical remark about implicit dereferences. Those usually (but not always) are used to add notes about a rule, rather than to carry semantic information. Moreover, attribute references do have a preference rule for implicit dereferences. And that rule is very clear:
...if the attribute_designator is for an attribute defined for (at least some) objects of an access type, then the prefix is never interpreted as an implicit_dereference; otherwise (...), if there is a prefix and the type of the name within the prefix is of an access type, the prefix is interpreted as an implicit_dereference.
The absence of this sort of statement for selected components makes it fairly clear that there is no preference rule for them.
Why then, did several members think such a preference existed? Most likely, it was the combination of the explicit preference rule for attributes, and the fact that no interpretations (for Ada 2022 or before) of a prefix of a selected component without an implicit dereference could be legal for an object of an access type. That is, a consequence of the rules is that an implicit dereference is always needed; it wasn’t actually a rule itself. Of course, when one changes the rules, the consequences may change as well -- but not people’s memories.
Given this situation, we need to again decide exactly what rule we want, and how to word it.
One option would be to make no wording change. That would allow prefixed views to be used with access types, and thus it would nicely make all types consistent. However, the compatibility and understandability reasons discussed in AI22-0091-1 still exist. Thus, this option was rejected.
A second option would be to add a preference rule somewhere (4.1? 4.1.3?) to implicit dereferences when possible. This was rejected as adding too much risk. We already know that attributes don’t allow implicit conversions in some cases of prefixes of access types; there may be other such cases buried somewhere.
A third option is to just disallow access types as the type T of the rule of 4.1.3(9.2/7) that defines prefixed views. This would copy exactly what AI22-0091-1 proposed. However, this had an unusual consequence that a prefixed view could be allowed for a private type but not allowed for its full type. At the time AI22-0091-1 was considered, this was considered unusual but not dangerous as prefixed views on the full type would simply not resolve and be rejected.
However, recently it was noted that an entirely different subprogram could be called in such cases. Consider:
package P2 is
type Cool_Obj is ...
function Foo (A : Cool_Obj) return Natural;
end P2;
with P2;
package P3 is
type Priv2 is private;
function Foo (A : Priv2) return Natural;
C2 : constant Priv2;
procedure Sink2 (Obj : in Natural := C2.Foo); -- (C)
private
type Priv2 is access all P2.Cool_Obj;
C2 : Priv2 := new P2.Cool_Obj;
Use_It : constant Natural := C2.Foo; -- (D)
end P3;
The prefixed view at (C) is the usual expected one, which calls the local Foo, P3.Foo. However, if access types cannot be used in a prefixed view, then (D) does not consider that alternative. Instead, it will consider the operations of the designated type, and thus it will call P2.Foo.
A reader of call (D) could easily be confused into thinking that P3.Foo (the nearby one that appears to match) will be called. This could be especially annoying in a child unit of P3 containing unit tests for P3. Such tests probably want to reflect the client view of P3, but they would not get that here.
Therefore, we decided to “split the baby” and allow prefixed views only on access types if they complete a private type. (An access type cannot complete a private extension, of course.) This eliminates the unusual case of prefixed views disappearing simply because of the type of a full type. And it keeps the compatibility impact to a minimum (since most access types do not complete a private type). Only an access-to-tagged type that completes a private type would be at risk for a compatibility problem (which is not very likely in any case, since it requires both the private type and the designated type to have operations with similar profiles).
In the example given above, the call at (D) would become ambiguous, as calls of both P2.Foo and P3.Foo would be considered. Since there is no way to differentiate them, the call would be ambiguous and thus illegal. This could be annoying, but it is way better than calling an unexpected routine. Note that this is rather similar to the way use-clause conflicts are handled — the confusing call becomes illegal.
We note that another (minor) advantage of this rule is that it means that anonymous access types cannot have prefixed views, a construct that would likely be more confusing than useful.
package P4 is
type Cool_Obj is ...
function Foo (A : Cool_Obj) return Natural;
end P4;
with P4;
package P5 is
type Priv3 is private;
function Foo (A : Priv3) return Natural;
function Bar (A : Priv3) return Natural;
C3 : constant Priv3;
procedure Sink2 (Obj : in Natural := C3.Foo); -- OK.
procedure Sink3 (Obj : in Natural := C3.Bar); -- OK.
private
type Priv3 is access all P4.Cool_Obj;
C3 : Priv3 := new P4.Cool_Obj;
Use_It1 : constant Natural := C3.Foo; -- Error: Ambiguous.
Use_It2 : constant Natural := C3.all.Foo; -- OK, calls P4.Foo.
Use_It3 : constant Natural := Foo(C3); -- OK, calls P5.Foo.
Use_It4 : constant Natural := C3.Bar; -- OK.
Use_It5 : constant Natural := C3.all.Bar; -- Error: no P4.Bar.
end P5;
With the chosen rule, prefixed views can be used on access types that complete a private type. That means that most prefixed views legal on the partial view remain legal when used on the full view (as with C3.Bar here). But if there is a conflict with an operation of the designated type, then the prefixed view becomes ambiguous and illegal. One can work around the problem with an explicit dereference (to call P4.Foo) or by reverting to a classic call.
@drepl
The @fa{prefix} (after any implicit dereference) shall resolve to denote an object or value of a specific tagged type @i{T} or class-wide type @i{T}'Class. The @fa{selector_name} shall resolve to denote a view of a subprogram declared immediately within the declarative region in which an ancestor of the type @i{T} is declared. The first formal parameter of the subprogram shall be of type @i{T}, or a class-wide type that covers @i{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 @fa{selected_component}. The subprogram shall not be an implicitly declared primitive operation of type @i{T} that overrides an inherited subprogram implemented by an entry or protected subprogram visible at the point of the @fa{selected_component}. The @fa{selected_component} denotes a view of this subprogram that omits the first formal parameter. This view is called a @i{prefixed view} of the subprogram, and the @fa{prefix} of the @fa{selected_component} (after any implicit dereference) is called the @i{prefix} of the prefixed view. In the special case where the prefix is class-wide and controlling, the prefixed view is not abstract, even if the unprefixed view is abstract; otherwise the prefixed view is abstract if and only if the unprefixed view is abstract.
@dby
The @fa{prefix} (with or without an implicit dereference) shall resolve to denote an object or value of a specific non-access type @i{T}, an access type @i{T} that completes a private type, or a class-wide type @i{T}'Class. The @fa{selector_name} shall resolve to denote a view of a subprogram declared immediately within the declarative region in which an ancestor of the type @i{T} is declared. The first formal parameter of the subprogram shall be of type @i{T}, or, if @i{T} is of a tagged type, a class-wide type that covers @i{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 type @i{T} visible at the point of the @fa{selected_component}. The subprogram shall not be an implicitly declared primitive operation of type @i{T} that overrides an inherited subprogram implemented by an entry or protected subprogram visible at the point of the @fa{selected_component}. The @fa{selected_component} denotes a view of this subprogram that omits the first formal parameter. This view is called a @i{prefixed view} of the subprogram, and the @fa{prefix} of the @fa{selected_component} (with or without an implicit dereference) is called the @i{prefix} of the prefixed view.
The untagged prefixed view tests should reflect this ruling, with the entire example included in a B-Test, and the legal cases of the example included in a C-Test..
This issue arose when Randy was exploring the options available for fixing the anomalies and conflicts that occur in the extension of user-defined indexing to untagged types. It was necessary to understand the similar cases that occur for prefixed views (since user-defined indexing is designed to be equivalent to prefixed views). And that led to carefully re-reading the associated resolution rules. He thanks (or blames?) Steve Baird for remarking that he didn’t think there was a preference to an implicit conversion, so he looked at that area in detail.