AI22-0091-1
!standard 4.1.3(9.1/2) 24-04-25 AI22-0091-1/02
!standard 4.1.3(9.2/3)
!reference AI12-0257-1
!class Amendment 23-12-07
!status work item 23-12-07
!status received 18-01-20
!priority Low
!difficulty Medium
!subject Generalize prefixed views
Prefixed views are allowed for all forms of types.
Ada users often are surprised that prefixed views cannot be used for abstractions that aren't tagged. That can lead to abstractions being declared tagged for no reason other than wanting prefixed notation (arguably, this was the case with the containers).
Prefixed views are allowed for all forms of types, not just tagged, though they are effectively not available for access types, because access values are implicitly dereferenced (so prefixed calls to subprograms with a first formal of a named access type won't resolve). This is not considered to be a significant limitation on usage.
Modify 4.1.3(9.1/2-9.2/3):
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{, 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 [tagged] 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 (after any implicit dereference) is called the prefix of the prefixed view.
Add after 4.1.3(9.2/3):
AARM Ramification: The notation of prefixed views is effectively available for all forms of type, with the exception of access types, since the rules specify that an implicit dereference is performed for access prefixes. If interpretations were allowed both with and without implicit dereference, this could lead to ambiguities for calls that were legal before prefixed views were generalized to allow them for untagged types, which would be a serious incompatibility.
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. End AARM Ramification.
We considered extending prefixed views to be allowed for just untagged private types (in addition to all tagged types), which initially seems appealing.
However, it would be unusual if the prefixed view weren't allowed at all on the full type of an untagged private type (unless the full type were tagged or access-to-tagged). That would mean that moving a subprogram previously defined in client code into the package of an abstraction could fail to compile if it used any prefixed views. (This could happen if the package was changed to be a child of the package that defines the type, for one example.)
Therefore, we do not consider extending untagged prefixed views to private types alone as a viable option.
An alternative would be to restrict untagged prefixed views to record types. That would require a means of specifying on a private type that the completion will be a record type, so that prefixed-view notation can be supported on the private type. This is necessary to avoid breaking privacy. Note that "tagged" plays this role in the current rules. (Without the notation, prefixed views would not be allowed on private types.)
To make this work, two options have been suggested:
An aspect:
type Priv is private with Prefix_Notation;
[As always, this is shorthand for "Prefix_Notation => True”.]
Or a keyword in place of “tagged":
type Priv is record private;
It's a little weird to affect resolution with an aspect (as it usually only affects Legality Rules), but it would not be too far outside of the model. Using "record" as a qualifier also seems a bit weird.
Ultimately it seems simpler to allow prefixed-view notation for all forms of types. The one case where this doesn't work well is for access types. Using a prefixed view for a call to a subprogram of an access type would be incompatible, because Ptr.Id could mean Id(Ptr) or Id(Ptr.all). In the current rules, Id(Ptr) is not considered, because an implicit dereference is applied by 4.1.3(9.2), so allowing prefixed views for access types both with and without implicit dereferencing could make some currently legal calls ambiguous.
To work around such an ambiguity would require falling back to "regular" call notation (meaning that a "use" clause or expanded-name notation might be needed). But the entire reason for using prefixed notation is to avoid those things, so this would be a nasty incompatibility.
We note that a second incompatibility is possible for any potential extension of prefixed notation to multiple forms of type, which is that if the prefix is overloaded with a mix of tagged and untagged types, a prefixed call could become ambiguous. In this case, the problem can be worked around by qualifying the prefix, which seems like an acceptable fix.
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. This has the effect that in places where the prefix is of a private type, no implicit dereference would happen, whereas in places where the prefix is visibly of an access type, the implicit dereference would be performed. This seems to nicely solve the upward compatibility problem, as well as allowing it to be used on private types that might be implemented by an access type.
This does defeat the ability to use prefix notation on primitive operations of a visible access type, but that doesn't seem like a great loss. There is also a small issue for default expressions for a "boundary" subprogram (where the spec is in a package visible part, and the body sees the full type), if the default expression uses prefix notation for a private type whose full type is an access type. But using "regular" call notation would solve that problem, so that also doesn't seem like a big loss.
The nice thing is that we aren't making any special rules for access types that have a partial view, but rather simply piggybacking on the normal situation that if you can "see" that the prefix is of an access type, then an implicit dereference occurs. If we wanted to be a bit friendlier, we could say that if there are not any interpretations for the selector_name with an implicit dereference, name resolution would fall back on an interpretation without the implicit dereference. We don't propose that for now, but it could be done as a later enhancement, since it would not create ambiguity; it would only make an otherwise illegal use of prefix notation into a legal one.
Note that neither the current nor the proposed wording of 4.1 require subprograms to be primitive in order to use prefixed notation, rather only that they be declared immediately within the same declarative region as the type. This is important for types that are declared in subprograms and blocks (for which no noninherited subprograms are primitive). This seems to be an important property to preserve, especially as it is much more likely for untagged types to be declared in scopes that are not packages.
procedure Vectors_Example is
generic
type Elem_Type is private;
package Vectors is
type Vector is private;
procedure Add_Element (V : in out Vector; Elem :
Elem_Type);
function Nth_Element (V : Vector; N : Positive) return
Elem_Type;
function Length (V : Vector) return Natural;
private
function Capacity (V : Vector) return Natural;
-- Return number of elements that may be added without
-- causing any new allocation of space
type Vector is array (1 .. 100) of Elem_Type
with Type_Invariant => Vector.Length <=
Vector.Capacity;
end Vectors;
package body Vectors is ... end Vectors;
package Int_Vecs is new Vectors(Integer);
V : Int_Vecs.Vector;
begin
V.Add_Element(42);
V.Add_Element(-33);
pragma Assert (V.Length = 2);
pragma Assert (V.Nth_Element(1) = 42);
end Vectors_Example;
There should be at least a B test and a C test, and those should cover the various new classes of (untagged) types that are allowed with prefixed views, such as record, private, scalar, access, and array types. Access cases would only occur in a B test, since prefixed calls using those will be illegal (but private types whose full type is an access type could be used in a C test).
See issue #53 on ARG GitHub issue list.
[a]Why did you add this part? It seems to conflict with the rule that allows subprograms from regions where an ancestor is declared, at least in the case of class-wide prefixes.
[b]That's actually from your proposed text (in AI22-0257), and I did wonder about that, but assumed you had a good reason for it. ;-) I probably should have asked you about it before proceeding to put it in the AI. Sounds like you don't think it should be there, but can you recall why you put it in? I agree that there's the potential for incompatibility with that wording, though I was having a little trouble formulating realistic examples where it would matter. I had thought of a convoluted case involving private types, but maybe there are simpler ones.
[c]Here is an example that is currently legal, but which violates this added phrase:
package P1 is
type T1 is tagged ...
procedure Pr1 (X : T1'Class);
end P1;
with P1;
package P2 is
type T2 is new P1.T1 with ...
procedure Pr2(X : T1'Class);
-- T1'Class is *not* declared
-- in this package
end P2;
with P2;
package P3 is
type T3 is new P2.T2 with ...
end P3;
with P3;
procedure Main is
X : T3;
begin
X.Pr1;
-- Still OK with new phrase
X.Pr2;
-- not OK with new phrase
end Main;
[d]I think the added phrase should probably be refined slightly. Perhaps:
A view of a subprogram whose first formal parameter {is of a class-wide type or} is of a [tagged] type {declared immediately within the same declarative region as the subprogram}, or is an access parameter whose designated type is [tagged]{such a type}:
[e]I had thought of basically that same example after posting my earlier comment. Your addition sounds like a reasonable way to address it. I guess one of us should make the change. I'm fine with you doing it if you want.
[f]OK, I have made the change. I am a bit unsure exactly how the wording works best, and whether there should be a comma. Anyway, what I ended up with was what seemed best after trying various alternatives.