Skip to content

Conversation

@denismazzucato
Copy link

Part of #159

@denismazzucato denismazzucato self-assigned this Oct 24, 2025
@denismazzucato denismazzucato changed the title Revision of direct attribute definitions Draft: Revision of direct attribute definitions Nov 6, 2025
@denismazzucato denismazzucato force-pushed the mr/oop-attributes branch 2 times, most recently from fa84d22 to 6258c6f Compare November 12, 2025 16:00
@denismazzucato denismazzucato changed the title Draft: Revision of direct attribute definitions Subprogram-valued Aspect Declarations Nov 12, 2025
Comment on lines 66 to 68
called directly. The name of the internal subprogram is determined solely by
the aspect name, meaning that the internal subprogram denoted by
``T'Write`` will have the same name of another declaration like ``U'Write``.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could pass unnoticed, let me highlight it with a comment: having the internal subprogram's name determined solely by the aspect name means that for two different types, declaring the same aspect with this proposal would create two homonym programs, even though declared for different types.

This is essential in type derivation since it permits overloadings, but it may produce name resolution errors that we didn't anticipate and that the user may find counterintuitive since the name would be different in the source code but not in the compiled program. I couldn't find any of such problem while writing this RFC but maybe by pointing out this small detail someone else will.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is essential in type derivation since it permits overloadings,

Can you give an example?

it may produce name resolution errors that we didn't anticipate and that the user may find counterintuitive since the name would be different in the source code but not in the compiled program.

But using the feature for two different types in the same scope is expected to work, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've a few ideas about how "nonoverridable aspects that denote subprograms" should be handled. I mean the same aspects that 13.1.1 (18.4/6) applies to in the current draft of Ada 202Y.

My feeling is that the current Ada rules about those aspects follows an underlying mental model that is rather more simple than the rules themselves. With this extension, we have an opportunity to make that mental model stand out more clearly.

I'll use Constant_Indexing as an example. Even though Constant_Indexing is a nonoverridable aspect, the subprograms it denotes can very much be overridden. For example:

package P is
   type T is tagged null record with Constant_Indexing => Index;

   function Index (X : T; I : Natural) return Natural;
end P;

package body P is
   function Index (X : T; I : Natural) return Natural is
   begin
      return 2 * I;
   end Index;
end P;

with P;

package Q is
   type T2 is new P.T with null record;

   overriding
   function Index (X : T2; I : Natural) return Natural;
end Q;

package body Q is
   function Index (X : T2; I : Natural) return Natural is
   begin
      return 3 * I;
   end Index;
end Q;

What Constant_Indexing being nonoverridable does prevent, however, is "removing an indexing profile" that exists for the parent type. For example, the following is rejected:

package P is
   type T is tagged null record with Constant_Indexing => Index;

   function Index (X : T; I : Natural) return Natural;
end P;

package body P is
   function Index (X : T; I : Natural) return Natural is
   begin
      return 2 * I;
   end Index;
end P;

with P;

package Q is
   type T2 is new P.T with null record with Constant_Indexing => Index2;

   function Index2 (X : T2; I : Float) return Float;
end Q;

package body Q is
   function Index2 (X : T2; I : Float) return Float is
   begin
      return 2.0 * I;
   end Index2;
end Q;

That's, under my interpretation, because (X : T; I : Natural) return Natural is removed from the indexing profiles in the type derivation.

Now, this is the mental model I think this suggests:

  1. To each type, we can associate a finite map. The keys are "the function profiles that match the constraint of Constant_Indexing for the type". (The associated values are functions complete with bodies, but that's less important.)
  2. When we derive a type T2 from a type T, we start off with the same map as T. To form the map of T2, we're allowed to add to T's map, to overwrite its elements, but we're not allowed to remove elements from it.

We can use that model to interpret the meaning of subprogram-valued aspect declarations for Constant_Indexing. For example:

package P is
   type T is tagged null record;

   function T'Constant_Indexing (X : T; I : Natural) return Natural;

   function T'Constant_Indexing (X : T; B : Boolean) return Natural;
end P;

package body P is
   function T'Constant_Indexing (X : T; I : Natural) return Natural is
   begin
      return 2 * I;
   end T'Constant_Indexing;

   function T'Constant_Indexing (X : T; B : Boolean) return Natural is
   begin
      return (if B then 2 else 3);
   end T'Constant_Indexing;
end P;

--  Given the package defined above, `P.T`'s map has exactly two keys,
--  "(X : T; I : Natural) return Natural" and "(X : T; B : Boolean) return Natural".

with P;

package Q is
   type T2 is new P.T with null record;

   --  We overwrite the value for "(X : T{,2}; I : Natural) return Natural".
   function T2'Constant_Indexing (X : T2; I : Natural) return Natural;

   --  We add a new key, "(X : T{,2}; F : Float) return Float".
   function T2'Constant_Indexing (X : T2; F : Float) return Float;
end Q;

package body Q is
   function T2'Constant_Indexing (X : T2; I : Natural) return Natural is
   begin
      return 3 * I;
   end T2'Constant_Indexing;

   function T2'Constant_Indexing (X : T2; F : Float) return Float is
   begin
      return 4.0 * F;
   end T2'Constant_Indexing;
end Q;

The map for Q.T2 has three profiles here and I find it pretty clear.

My point is that as long that we can find a way to describe this feature that follows this general interface, it would be adequate and we don't necessarily have to use an equivalence rule in terms of an implicit name.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Ronan's point of view, that the type specified as the prefix of the name is part of the name from an overload resolution point of view, and can be used to distinguish at a call site. On the other hand, as far as what is the equivalent aspect specification, I would suggest it is something like:

with Constant_Indexing => @'Constant_Indexing

where "@" is filled in with the associated type's name. So the "name" specified for the aspect is non-overridable, but a special syntax is used to refer to a subprogram that is declared with one of these proposed new subprogram-valued aspect declarations.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Ronan for the detailed example you presented above, I also agree with your analysis.

And to your suggestion Tucker, Instead of "@" is filled in with the associated type's name, I'd rephrase it as: in @'Constant_Indexing the placeholder @ is filled with the name of the ancestor of the associated type (allowing itself as "first" ancestor here) which first specified Constant_Indexing. This modification is required to make T'Constant_Indexing and T2'Constant_Indexing share the same implicit name, but not with Other'Constant_Indexing to avoid name clashes with other unrelated entities.

I don't know if this is going against Ronan's idea of not having to use an equivalence rule in terms of an implicit name (in this case the implicit name is the special @'Constant_Indexing here). But I think with this use of @ we could give a more intuitive and less case-by-case definition for this proposal, it would be simply the equivalence with its extension in terms of @ and then we define what @ is replaced by. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Ronan for the detailed example you presented above, I also agree with your analysis.

And to your suggestion Tucker, Instead of "@" is filled in with the associated type's name, I'd rephrase it as: in @'Constant_Indexing the placeholder @ is filled with the name of the ancestor of the associated type (allowing itself as "first" ancestor here) which first specified Constant_Indexing. This modification is required to make T'Constant_Indexing and T2'Constant_Indexing share the same implicit name, but not with Other'Constant_Indexing to avoid name clashes with other unrelated entities.

Actually, I realize what I wrote was ambiguous. What I meant is that the name used in the equivalent aspect specification is literally "@'<aspect_name>" but when you write the subprogram-valued declaration, the programmer replaces the "@" with the particular type name for which it is being declared. Furthermore, when one of these subprograms is inherited, the @ is replaced with the new (derived) type's name in the implicit declaration, similar to the way the formal parameter types are replaced.

I don't know if this is going against Ronan's idea of not having to use an equivalence rule in terms of an implicit name (in this case the implicit name is the special @'Constant_Indexing here). But I think with this use of @ we could give a more intuitive and less case-by-case definition for this proposal, it would be simply the equivalence with its extension in terms of @ and then we define what @ is replaced by. What do you think?

Yes, that is what I was trying to suggest.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, let me know if you like how I phrased it in the rfc.

Comment on lines 66 to 68
called directly. The name of the internal subprogram is determined solely by
the aspect name, meaning that the internal subprogram denoted by
``T'Write`` will have the same name of another declaration like ``U'Write``.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is essential in type derivation since it permits overloadings,

Can you give an example?

it may produce name resolution errors that we didn't anticipate and that the user may find counterintuitive since the name would be different in the source code but not in the compiled program.

But using the feature for two different types in the same scope is expected to work, right?

==========

Currently, to specify a subprogram-valued aspect for a type, one needs to first
declare a subprogram, that most of the times is never referred elsewhere, and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: "referred elsewhere" -> "referred to elsewhere".

==========================

While the notation "@'<Aspect_Name>" is not a syntactic Ada construct and it is
used above in thi proposal at an intuitive level for the aspect specifications,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: "thi" -> "this.

Comment on lines +139 to +157
Subprogram-valued aspect declarations can be used to define both primitive and
nonprimitive operations of a type.
For instance, we could define a container type as:

.. code-block:: ada
type Container is tagged record
...
end record;
type Cursor is new Positive;
function Container'First (C : Container) return Cursor;
function Container'Next (C : Container; Pos : Cursor) return Cursor;
function Container'Has_Element (C : Container; Pos : Cursor) return Boolean;
function Container'Element (C : Container; Pos : Cursor) return Integer;
All the subprogram-valued aspect
declarations of ``Container`` are primitive operations.
The usual rules of primitive operations apply.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you give an second example, for the nonprimitive case? (The one already here is a translation of the GNAT-specific Iterable aspect I think, and it does require the subprograms to be primitive operations).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would occur for all aspects that require a subprogram as value and that subprogram needs not to have a profile that will make them primitive. Currently, we can find such examples with class-wide aspects, such as:

   type T is tagged null record;
   
   --  nonprimitive
   procedure T'Class'Write
     (Stream : not null access Ada.Streams.Root_Stream_Type'Class;
      Item   : T'Class) is null;

But in the future more may be added, and I couldn't find anything in the RM that requires a subprogram used as aspect value that needs to be primitive.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants