From 32b7185854b11d48f0fd447d611429fe9dafe048 Mon Sep 17 00:00:00 2001 From: Quentin Ochem Date: Fri, 12 Sep 2025 10:00:16 -0400 Subject: [PATCH 1/5] updated constructor proposal --- features/rfc-oop-constructors.rst | 425 ++++++++++++++---------------- 1 file changed, 198 insertions(+), 227 deletions(-) diff --git a/features/rfc-oop-constructors.rst b/features/rfc-oop-constructors.rst index 8210762..6ed7029 100644 --- a/features/rfc-oop-constructors.rst +++ b/features/rfc-oop-constructors.rst @@ -1,6 +1,7 @@ - Feature Name: Standard OOP model - Start Date: May 5th 2020 -- Status: Production +- RFC PR: +- RFC Issue: Summary ======= @@ -25,53 +26,13 @@ For example: package P is type T1 is tagged null record; - for T1' with Constructor => Constr; - procedure Constr (Self : in out T1); - procedure Constr (Self : in out T1; Some_Value : Integer); + procedure T1'Constructor (Self : in out T1); + procedure T1'Constructor (Self : in out T1; Some_Value : Integer); type T2 is null record; - for T2'Constructor use Constr; - procedure Constr (Self : in out T2; Some_Value : Integer); - end P; - -Note that as for all attributes, the Ada 2012 syntax is available (and used in -the rest of that document): - -.. code-block:: ada - - package P is - type T1 is tagged null record - with Constructor => Constr; - - procedure Constr (Self : in out T1); - procedure Constr (Self : in out T1; Some_Value : Integer); - - end P; - -Once a constructor name is chosen with the "Constructor" aspect, all primitives -declared for this type with the constructor name are considered constructor, it -is an error to declare a primitive of that type for that name that doesn't -match the expected profile. Constructors cannot be called directly, it is an -error to refer to that constructor through a call or aspect. - -Every type can decide on different constructor names, children can have -different names than parent (coding style may impose nomenclatura here). For -example: - -.. code-block:: ada - - package P is - type Root is tagged null record - with Constructor => Root_Constr; - - procedure Root_Constr (Self : in out T1); - procedure Root_Constr (Self : in out T1; Some_Value : Integer); - - type Child is new Root with null record with Constructor => Child_Constr; - - procedure Child_Constr (Self : in out T2; Some_Value : Integer); + procedure T2'Constructor (Self : in out T2; Some_Value : Integer); end P; As soon as a constructor exist, an objects cannot be created without calling one @@ -85,7 +46,7 @@ object on the heap. E.g: V : T1; -- OK, parameterless constructor V2 : T1 := T1'Make(42); -- OK, 1 parameter constructor - type T1_Ref is acecss all T1'Class; + type T1_Ref is access all T1'Class; V3 : T1_Ref := new T1; V4 : T1_Ref := new T1'Make(42); @@ -97,25 +58,11 @@ are called first, before their containing object. The `'Make'` function is automatically generated by the compilers. It has the same parameters, default values and default expressions as the constructor it calls, with the exception of the first parameter. T'Make is not intrinsic, -it has the same convension as a function of the same profile, and can be used +it has the same convention as a function of the same profile, and can be used e.g. as a generic parameter or through a access to subprogram. Note that constructors will be "scoped" in a future extension of the RFC, but -the scoping notation isn't strictly necessary to implement initial semantics: - -.. code-block:: ada - - package P is - type T1 is tagged record - procedure T1'Constructor (Self : in out T1); - procedure T1'Constructor (Self : in out T1; Some_Value : Integer); - end record; - - - type T2 is record - procedure T2'Constructor (Self : in out T2; Some_Value : Integer); - end record; - end P; +the scoping notation isn't strictly necessary to implement initial semantics. Constructor as a Function ------------------------- @@ -135,14 +82,14 @@ for a generic parameter or an access-to-subprogram. For example: end; package P is - type T1 is tagged null record with Constructor => Constr; + type T1 is tagged null record; - procedure Constr (Self : in out T1); - procedure Constr (Self : in out T1; Some_Value : Integer); + procedure T1'Constructor (Self : in out T1); + procedure T1'Constructor (Self : in out T1; Some_Value : Integer); - type T2 is null record record with Constructor => Constr; + type T2 is null record record; - procedure Constr (Self : in out T2; Some_Value : Integer); + procedure T2'Constructor (Self : in out T2; Some_Value : Integer); type Acc1 is access function (Some_Value : Integer) return T1; @@ -170,9 +117,9 @@ initialized from a copy. For example: .. code-block:: ada package P is - type T1 is tagged null record with Constructor => Constr; + type T1 is tagged null record; - procedure Constr (Self : in out T1; Source : T1); + procedure T1'Constructor (Self : in out T1; Source : T1); If not specified, a default copy constructor is automatically generated. The implicit copy constructor will call the parent copy constructor, then copy @@ -210,37 +157,36 @@ in the constuctor body, For example: .. code-block:: ada - type Root is tagged null record with Constructor => Constr; - procedure Constr (Self : in out Root; V : Integer); + type Root is tagged null record; + procedure Root'Constructor (Self : in out Root; V : Integer); - type Child is new Root with null record with Constructor => Constr; - procedure Constr (Self : in out Child); + type Child is new Root with null record; + procedure Child'Constructor (Self : in out Child); - procedure Constr (Self : in out Child) - with Super (42) + procedure Child'Constructor (Self : in out Child) + with Super => (42) is begin null; - end Constr; + end Child'Constructor; Note that the constructor of an abstract type can be called here, for example: .. code-block:: ada - type Root is abstract tagged null record with Constructor => Constr; - procedure Constr (Self : in out Root; V : Integer); - - type Child is new Root with null record with Constructor => Constr; - procedure Constr (Self : in out Child); + type Root is abstract tagged null record; + procedure Root'Constructor (Self : in out Root; V : Integer); + type Child is new Root with null record; + procedure Child'Constructor (Self : in out Child); - procedure Constr (Self : in out Child) + procedure Child'Constructor (Self : in out Child) -- Root'Make can be called here to initialize Super - with Super (42) + with Super => (42) is begin null; - end Constr; + end Child'Constructor; When valuating values in the Super aspect, the constructed object does not exit yet. It is illegal to refer to this parameter in the aspect. @@ -263,28 +209,28 @@ Initialization of components can be done in two ways: - Through the default value provided at component declaration. - Through an ``Initialize`` aspect that can rely on constructor parameters. -If the component is of a type that doesn't have a parameterless constructor, it has -to be initialized by on of these two mechanism. +If the component is of a type that doesn't have a parameterless constructor, it +has to be initialized by on of these two mechanism. Here's an example of using ``Initialize`` for such a case: .. code-block:: ada - type Some_Type is tagged null record with Constructor => Constr; - procedure Constr (Self : in out C; Some_Value : Integer); + type Some_Type is tagged null record; + procedure Some_Type'Constructor (Self : in out C; Some_Value : Integer); type C is tagged record F : Some_Type; - end record with Constructor => Constr; + end record; - procedure Constr (Self : in out C; V : Integer); + procedure C'Constructor (Self : in out C; V : Integer); - procedure Constr (Self : in out C; V : Integer) - with Initialize (F => Some_Type'Make (V)) + procedure C'Constructor (Self : in out C; V : Integer) + with Initialize => (F => Some_Type'Make (V)) is begin null; - end Constr; + end C'Constructor; Note that if there is no initialization for components with no default @@ -292,8 +238,8 @@ constructors, the compiler will raise an error: .. code-block:: ada - type Some_Type is tagged null record with Constructor => Constr; - procedure Constr (Self : in out C; Some_Value : Integer); + type Some_Type is tagged null record; + procedure Some_Type'Constructor (Self : in out C; Some_Value : Integer); type C is tagged record F : Some_Type; -- Compilation error, F needs explicit constructor call @@ -315,23 +261,23 @@ initialized as described at declaration time. For example: type C is tagged record A : Integer := Print_And_Return ("A FROM RECORD"); B : Integer := Print_And_Return ("B FROM RECORD"); - end record with Constructor => Constr; + end record; - procedure Constr (Self : in out C); - procedure Constr (Self : in out C; S : String); + procedure C'Constructor (Self : in out C); + procedure C'Constructor (Self : in out C; S : String); - procedure Constr (Self : in out C) + procedure C'Constructor (Self : in out C) is begin null; - end Constr; + end C'Constructor; - procedure Constr (Self : in out C; S : String) - with Initialize (A => Print_And_Return (S)) + procedure C'Constructor (Self : in out C; S : String) + with Initialize => (A => Print_And_Return (S)) is begin null; - end Constr; + end C'Constructor; V1 : C := C'Make; -- Will print A FROM RECORD, B FROM RECORD V2 : C := C'Make ("ATERNATE A"); -- Will print ATERNATE A, B FROM RECORD @@ -354,16 +300,16 @@ others, it is possible to initialize limited types: type C is limited tagged record F : R; - end record with Constructor => Constr; + end record; - procedure Constr (Self : in out C); + procedure C'Constructor (Self : in out C); - procedure Constr (Self : in out C) - with Initialize (F => (1, 2)) + procedure C'Constructor (Self : in out C) + with Initialize => (F => (1, 2)) is begin null; - end Constr; + end C'Constructor; The only components that a constructor can initialize in the initialization list are its own. Parent components are supposed to be initialized by the parent @@ -377,11 +323,12 @@ object. The following for example will issue an error: type Child is new Root with record C : R; - end record with Constructor => Constr; - procedure Constr (Self : in out Child); + end record; - procedure Constr (Self : in out Child) - with Initialize ( + procedure Child'Constructor (Self : in out Child); + + procedure Child'Constructor (Self : in out Child) + with Initialize => ( A => 1, -- Compilation Error B => 2, -- Compilation Error C => 3 -- OK @@ -389,7 +336,7 @@ object. The following for example will issue an error: is begin null; - end Constr; + end Child'Constructor; When valuating values in the Initialize aspect, the constructed object does not exit yet. It is illegal to refer to this parameter in the aspect. The following @@ -399,17 +346,17 @@ is illegal: type Root is record A, B : Integer; - end record with Constructor => Constr; + end record; - procedure Constr (Self : in out Root) - with Initialize ( + procedure Root'Constructor (Self : in out Root) + with Initialize => ( A => 1, -- OK B => Self.A -- Compilation Error ) is begin null; - end Constr; + end Root'Constructor; Valuation of Discriminants @@ -428,8 +375,9 @@ of legal and illegal code: type T2 (L : Integer) is tagged record X : Some_Array (0 .. L); - end record with Constructor => Constr; - procedure Constr (Self : in out T2); + end record; + + procedure T2'Constructor (Self : in out T2); V1 : T1 (10); -- legal V2 : T2 (10); -- compilation error @@ -443,15 +391,16 @@ initialization list. For example: package P is type T2 (L : Integer) is tagged record X : Some_Array (0 .. L); - end record with Constructor => Constr; - procedure Constr (Self : in out T2; Size : Integer); + end record; + + procedure T2'Constructor (Self : in out T2; Size : Integer); - procedure Constr (Self : in out T2; Size : Integer) - with Initialize (L => Size - 1) + procedure T2'Constructor (Self : in out T2; Size : Integer) + with Initialize => (L => Size - 1) is begin null; - end Constr; + end T2'Constructor; V2 : T2 := T2'Make (10); end P; @@ -462,12 +411,14 @@ constructors, the parent type discriminants are not set. For example: .. code-block:: ada - type Root (V : Integer) is tagged null record with Constructor => Constr; - procedure Constr (Self : in out Child); + type Root (V : Integer) is tagged null record; + + procedure Root'Constructor (Self : in out Child); -- note that we're not specifying Root discriminant as Root has a constructor - type Child is new Root with null record with Constructor => Constr; - procedure Constr (Self : in out Child); + type Child is new Root with null record; + + procedure Child'Constructor (Self : in out Child); Here's a full example demonstrating both a regular use of discriminant and a use with the new notation: @@ -486,32 +437,33 @@ with the new notation: type New_Root (L_Root : Integer) is tagged record V : String (1 .. L_Root); - end record with Constructor => Constr; + end record; - procedure Constr (Self : in out New_Root; L : Integer); + procedure New_Root'Constructor (Self : in out New_Root; L : Integer); type New_Child (L_Child_2 : Integer) is new New_Root with record W : String (1 .. L_Child_2); - end record with Constructor => Constr; - procedure Constr (Self : in out New_Child; L1, L2 : Integer); + end record; + + procedure New_Child'Constructor (Self : in out New_Child; L1, L2 : Integer); end P; package body P is - procedure Constr (Self : in out New_Root; L : Integer) - with Initializes (L_Root => L) + procedure New_Root'Constructor (Self : in out New_Root; L : Integer) + with Initialize => (L_Root => L) is begin null; - end; + end New_Root'Constructor; - procedure Constr (Self : in out New_Child; L1, L2 : Integer) - with Super (L1), Initializes (L_Child_2 => L2) + procedure New_Child'Constructor (Self : in out New_Child; L1, L2 : Integer) + with Super => (L1), Initialize => (L_Child_2 => L2) is begin null; - end; + end New_Child'Constructor; end P; @@ -542,13 +494,13 @@ cannot however be used to create a value. For exmample: when False => B, C : Integer; end case; - end record with Constructor => Constr; + end record; - procedure Constr (Self : in out Bla; Val : Boolean) - with Initialize (V => Val); + procedure Bla'Constructor (Self : in out Bla; Val : Boolean) + with Initialize => (V => Val); is null; - end Constr; + end Bla'Constructor; V1 : Bla := V'Make (True); -- OK, that's what we want V2 : Bla (True); -- NOK, this needs an explicit discriminant check @@ -596,14 +548,16 @@ constructors are provided. For example: .. code-block:: ada type T1 is tagged record - + null; end record; - type T2 is tagged null record with Constructor => Constr; - procedure Constr (Self : in out T1, X : Integer); + type T2 is tagged null record; - type T3 is new T2 with null record with Constructor => Constr; - procedure Constr (Self : in out T1, X : Integer, Y : Integer); + procedure T2'Constructor (Self : in out T1, X : Integer); + + type T3 is new T2 with null record; + + procedure T3'Constructor (Self : in out T1, X : Integer, Y : Integer); V1 : T1; -- OK V2a : T2; -- Compilation error, no parameterless constructor is present @@ -614,96 +568,100 @@ constructors are provided. For example: Constructors and Generics ------------------------- -A type used an as a actual of a formal generic parameter is expected to have -a parameterless constructor. This is necessary to enable proper derivation and -allocation. For example: - -.. code-block:: ada - - generic - type T is tagged record; - package G is - V : T; - end G; - - package P is +Generic formal constructor follow similar syntax and rules as when actual +constructors are declared. Notably: - type T1 is tagged null record; - procedure T1 (Self : in out T1); +- A tagged type, when not provided with any specific indication, is expected + to have a parameterless and a copy constructor. +- When an explicit constructor is added to the list of generic formal + constructors, no parameterless constructor is required by the generic formal. +- Requirement on parameterless and by copy constructors can be removed by + marking them abstract. - type T2 is tagged null record; - procedure T2 (Self : in out T1; V : Integer); +As for subsprograms, generic formal constructors are introduced with the `with` +reserved word. For example: - package G1 is new G (T1); -- Legal - package G2 is new G (T2); -- Illegal, T2 doesn't have a parameterless constructor +.. code-block:: ada - end P; + generic + type T1 is tagged private; + -- Needs at least a parameterless and a by-copy constructor -The syntax to provide a constructor on a tagged type is similar to a scopeless -constructor - it's a formal procedure of the name of the type, that takes -an in out reference to the type as first parameter: + type T2 is tagged private; + with T2'Constructor (Self : in out T2; V : Integer); + -- No parameterless constructor expected, but a by-copy one -.. code-block:: ada + type T3 is tagged private; + with T2'Constructor (Self : in out T2) is abstract; + -- No parameterless constructor expected, but a by-copy one - generic - type T is tagged record with Constructor => Constr; - with procedure Constr (V : Integer) return T; + type T4 is tagged private; + with T4'Constructor (Self : in out T2) is abstract; + with T4'Constructor (Self : in out T2; Src : T2) is abstract; + -- No parameterless constructor expected, but a by-copy one package G is - V : T := T'Make (55); + V : T1; -- OK, we have parameterless constructor + V2 : T1 := V; -- OK, we have by-copy constructor + + V3 : T4; -- NOK we don't have parameterless constructor for T4 end G; package P is - type T2 is tagged null record with Constructor => Constr; - procedure Constr (Self : in out T1; V : Integer); - - package G2 is new G (T2, T2'Make); -- Legal + type R1 is tagged null record; + procedure R1'Constructor (Self : in out R1); + procedure R1'Constructor (Self : in out R1; V : Integer); + + type R2 is tagged null record; + procedure R2'Constructor (Self : in out R2; V : Integer); + procedure R2'Constructor (Self : in out R2; Src : R2) is abstract; + + package G1 is new G ( + T1 => R1, + T2 => R1, + T3 => R1, + T4 => R1 + ); + -- All of these are OK, T1 provides all the necessary constructors + + package G2 is new G ( + T1 => R2, -- Error, R2 doesn't have parametelress and by copy constructor + T2 => R2, -- Error, R2 doesn't have parametric and by copy constructor + T3 => R2, -- Error, R2 doesn't by copy constructor + T4 => R2 -- OK, no constructor expected here + ); end P; -Types without parameterless constructors must either have explicit constructors -declared, or be declared as indefinite type (ie they can't be instanciated in -by the generic). +Note that the notation: .. code-block:: ada generic - type T (<>) is tagged record; - package G is - procedure Proc (V : T) - end G; - - package P is - - type T1 is tagged null record with Constructor => Constr; - procedure Constr (Self : in out T1); - - type T2 is tagged null record with Constructor => Constr; - procedure Constr (Self : in out T1; V : Integer); - - package G1 is new G (T1); -- Legal - package G2 is new G (T2); -- Legal - - end P; + type T1 is tagged private; +Accept both by-constructors and non-by constructor types. However, +by-constructor types would need to provide a parametelress constructor and a +copy constructor to be accepted as formal parameters. Removing Constructors from Public View -------------------------------------- A special syntax is provided to remove the default parameterless constructor -from the public view, without providing any other constructor. The full view of a -type is then responsible to provide constructor (with or without parameters). +from the public view, without providing any other constructor. The full view of +a type is then responsible to provide constructor (with or without parameters). Such object can only be created by code that has visibility over the private section of the package: .. code-block:: ada package P is - type T1 is null record with Constructor => Constr; - procedure Constr (Self : in out T1) is abstract; + type T1 is null record; + + procedure T1'Constructor (Self : in out T1) is abstract; private - procedure Constr (Self : in out T1); + procedure T1'Constructor (Self : in out T1); end P; Tagged Hierarchy Consistency @@ -721,15 +679,26 @@ type by a "by constructor" tagged type, e.g.: type New_Child is new New_Root with record null; - end record with Constructor => Constr; + end record; - procedure Constr (Self : in out New_Child; L1, L2 : Integer); + procedure New_Child'Constructor (Self : in out New_Child; L1, L2 : Integer); In that case, any child of New_Child has to be a by-constructor type, ie it while it is possible to extend a "regular" tagged type by a "by constructor" tagged type, it is not possible to extend a "by constructor" tagged type by a regular one. +Initialization +-------------- + +In certain situations, it's important to know if an object is considered +initialized. For example, this can clarify wether passing a value of such object +may lead to errors. + +An object value in a constructor is consisered initialized once the `Super` and +`Initialze` aspects have been computed. Formally, the role of the constructor +is to establish further properties than the initialization. + Reference-level explanation =========================== @@ -771,33 +740,33 @@ copies: type T (S : Integer) is tagged record Content : Pos_Array (1..S); - end record with Constructor => Constr; + end record; - procedure Constr (Self : in out T; S : Integer); + procedure T'Constructor (Self : in out T; S : Integer); type U (S2 : Integer) is new T with record Content_2 : Pos_Array (1..S2); - end record with Constructor => Constr; + end record; - procedure Constr (Self : in out T); + procedure U'Constructor (Self : in out T); end Test; package body Test is - procedure Constr (Self : in out T; S : Integer) - with Initializes (S => S * 2); + procedure T'Constructor (Self : in out T; S : Integer) + with Initialize => (S => S * 2); is begin Self.Content := (others => 12); - end Constr; + end T'Constructor; - procedure Constr (Self : in out U) - with Initializes (S2 => 12) - Super (S => 15) + procedure U'Constructor (Self : in out U) + with Initialize => (S2 => 12) + Super => (S => 15) is begin Self.Content2 := (others => 18); - end Constr; + end U'Constructor; end Test; @@ -861,6 +830,7 @@ copies: end Test; + Drawbacks ========= @@ -887,11 +857,12 @@ generics. We could consider allowing: package P is type T1 (<>) is tagged record -- T1 is indefinite X : String; - end record with Constructor => Constr; - procedure Constr (Val : String); + end record; - procedure Constr (Val : String) - with Initialize (X => Val); + procedure T1'Constructor (Self : T1; Val : String) + + procedure T1'Constructor (Self : T1; Val : String) + with Initialize => (X => Val); begin null; end Constr; @@ -914,21 +885,21 @@ Consider the following hierarchy: when False => B : Integer; end case; - end record with Constructor => Constr; + end record; - procedure Constr (Self : in out Bla; C : Boolean) - with Initialize (D => C); + procedure Root'Constructor (Self : in out Bla; C : Boolean) + with Initialize => (D => C); is null; - end Constr; + end Root'Constructor; type Child is new Root with null record with Constructor => Constr; - procedure Constr (Self : in out Bla; C : Boolean) - with Super (C); + procedure Child'Constructor (Self : in out Bla; C : Boolean) + with Super => (C); is null; - end Constr; + end Child'Constructor; Child does not have any discrimininant. Root discriminant is set by its own constructor. There is currently no syntax allowing to subtype Child and provide From 2833fe6f27f632b0a8b8c6f5fddfb30bfea7bcba Mon Sep 17 00:00:00 2001 From: Quentin Ochem Date: Fri, 12 Sep 2025 10:21:05 -0400 Subject: [PATCH 2/5] fixed destructor syntax --- features/rfc-oop-destructors.rst | 41 +++++--------------------------- 1 file changed, 6 insertions(+), 35 deletions(-) diff --git a/features/rfc-oop-destructors.rst b/features/rfc-oop-destructors.rst index d7dcac0..bd72eaf 100644 --- a/features/rfc-oop-destructors.rst +++ b/features/rfc-oop-destructors.rst @@ -12,20 +12,18 @@ Guide-level explanation ======================= Record, tagged records and class records can declare destructors. The -destructor is identifed by the `Destructor` attribute, e.g.: +destructor is identifed by the `'Destructor` attribute, e.g.: .. code-block:: ada package P is type T is tagged null record; - for T'Destructor use T_Destructor; - procedure T_Destructor (Self : in out T); + procedure T'Destructor (Self : in out T); - type T2 is new T with null record - with Destructor => T2_Destructor; + type T2 is new T with null record; - procedure T2_Destructor (Self : in out T2); + procedure T2'Destructor (Self : in out T2); end P; The destructor expands into a Finalizable type, and the runtime semantics can @@ -44,30 +42,6 @@ The expansion is meant to allow the following: qualifier. We deem that it would be confusing wrt. the auto call parent semantics. -.. attention:: The original example above was: - - .. code-block:: ada - - package P is - type T is tagged null record; - for T'Destructor use My_Destructor; - - procedure My_Destructor (Self : in out T); - - type T2 is new T with null record - with Destructor => My_Destructor; - - procedure My_Destructor (Self : in out T2); - end P; - - Which seems wrong whichever way you look at it because both destructors - have the same name. Either you want to disallow overriding the destructor - subprogram itself, either you want to allow overriding but disallow - respecification of the aspect. - - The question being, which seems more natural ? I would say that we want to - forbid overriding the subprogram itself, and use the expansion shown below. - Here is a proposed expansion for the example above: .. code-block:: ada @@ -114,16 +88,13 @@ Reference-level explanation Name resolution rules --------------------- -* The ``Destructor`` aspect expects a procedure with a single parameter of the +* The ``Destructor`` attribute expects a procedure with a single parameter of the type on which the aspect is defined. Legality rules -------------- -* It is forbidden to override a procedure specified as a value for the - `Destructor` aspect. - -* The `Destructor` aspect can be re-specified for types derived from a type +* The `Destructor` attribute can be re-specified for types derived from a type that has a `Destructor` aspect. * The subprogram passed to the destructor aspect should have the ``in out`` From 42e7945d8a6c5f080336892d932c05b77ca1b097 Mon Sep 17 00:00:00 2001 From: Quentin Ochem Date: Fri, 12 Sep 2025 10:22:07 -0400 Subject: [PATCH 3/5] added initial proposal for aggregates and assignments --- .../rfc-oop-aggregates-and-assignments.rst | 637 ++++++++++++++++++ 1 file changed, 637 insertions(+) create mode 100644 features/rfc-oop-aggregates-and-assignments.rst diff --git a/features/rfc-oop-aggregates-and-assignments.rst b/features/rfc-oop-aggregates-and-assignments.rst new file mode 100644 index 0000000..e45d575 --- /dev/null +++ b/features/rfc-oop-aggregates-and-assignments.rst @@ -0,0 +1,637 @@ +- Feature Name: +- Start Date: +- RFC PR: +- RFC Issue: + +Summary +======= + +Motivation +========== + +Issue at hand +------------- + +Aggregates and assignments share a few joint issues that make it worth combining +into a single RFC. To understand, it's worth starting with the issues with the +current semantics. + +First, in Ada, aggregates are a way to completely workaround calls of +initialization. To some respect, this makes sense, aggregates are ways to +replace initialization. But the consequence is that there's no way to ensure +that a given sequence of statement is putting an object in a consistent state +at creation time (unlike traditional constructors). + +Second Adjust perform a post-copy update to a type. This causes a double issue, +first in terms of performance, as assignment may not need all components to be +modified. But this also limits the control over assignment logic, as the user +has no way to know what was the initial state of the object or what object +was initially copied from. + +Third, Ada allows partial assignment of objects through parent views. To +some respect, this is also an issue, as the resulting object may be inconsistent, +with only part updated, and potentially now way in Adjust to understand which +part was changed and which part was not. + +A related issue is the so called "Aggregate by extension" where a root object +is copied into a child one with specific values provided by the aggregate, +again here with no control over the consistency of values (not even in Adjust +in the case of initialiation). + +To solve these issues, we propose to introduce a two step object update +mechanism through a value duplication ('Clone) and post update adjustment +('Adjust). + +Note that this extra complexity is driven from the desire to support natively +Ada constructions (aggregates, partial copie, etc) and improve compatibility +between classes and tagged types. Users can leverage default implementation if +such level of control is unecessary. Some language extension may also allow +to forbig aggregates and partial update on specific types (although this +introduces complexities in generics that now need to specify wether these +restricted types are allowed or not). + +Also keep in mind that Ada Flare aggregates also need to account for types that +have both public and private components. + +This RFC is about tagged record (and class records even if not explicitely +mentionned). Simple records should also be studied when constructors are made +available to them. + +The additional capabilities need to be optimized as much as possible by the +compiler. In particular - even if it's not a language mandate - the compiler +should replace calls to Clone by binary copies and remove calls to Adjust when +it knows there's no chance of calling an overriden subprogram. + +'Clone +------ + +The attribute 'Clone can be defined for each tagged type. It describes how to +copy (or clone) the value of a tagged type into a other one. For example: + +.. code-block:: ada + + type Root is tagged record + A : access Integer; + end record; + + procedure Root'Clone (Self : Root; To : in out Root); + +Root'Clone is not a primitive and cannot be inherited. However, a child +class may provide its own cloning method: + +.. code-block:: ada + + type Child is new Root with record + B : access Integer; + end record; + + procedure Child'Clone (Self : Child; To : in out Child); + +The default implementation of Clone first calls the parent clone and then +calls clone operation of all the components one by one. The compiler is free to +optimize to bitwise copies if clone operations are not user-defined. + +Calls to 'Clone are statically resolved when used on definite views, and +dynamcially resolved on 'Class wide type. This is arguably a departure from the +"all calls are dispatching" requirement from other aspects of the OOP design, +but is required to allow partial copies of objects which are done today in +various places Ada. + +The invariant of the target object is not checked after a call to Clone, some +parts may still be inconsistent and fixed later by Adjust. + +'Adjust +------- + +'Adjust is a overridable attribute called after certain operations. It is +different from the legacy Ada Adjust primitive in that it has an argument +refering to the initial value. Note that the From parameter of adjust is +always typed after the root type of the tagged record hierarchy - indeed, the source +object may be higher up in the derivation chain in the case of partial +copy. This value value is provided for reference but is not expected to be +modified. + +.. code-block:: ada + + type Root is tagged record + A : access Integer; + end record; + + procedure Root'Adjust (Self : in out Root; From : Root); + + type Child is new Root with record + B : access Integer; + end record; + + procedure Child'Adjust (Self : in out Child; From : Root); + +Values of the From parameter will have been copied from Clone call prior to +calling Adjust. + +Invariants are checked after a call to Adjust. + +Base code for the Examples +-------------------------- + +To reason on the examples below, it's useful to consider a simple hierarchy +with pointers as components, where these pointers are supposed to be unique +and deallocated upon destruction. In addition, the pointed value of the child +needs to be maintained equal to the parents. + +.. code-block:: ada + + type Root is tagged record + A : access Integer; + end record; + + procedure Root'Constructor (Self : in out Root) is + begin + Self.A := new Integer'(0); + end Root'Constructor; + + procedure Root'Clone (Self : Root; To : in out Root) is + begin + Free (To.A); + To.A := new Integer'(Self.A.all); + end Root'Clone; + + procedure Root'Adjust (Self : in out Root; From : Root) is + begin + null; + end Root'Adjust; + + type Child is new Root with record + B : access Integer; + end record; + + procedure Child'Constructor (Self : in out Child) is + begin + Self.B := new Integer'(0); + end Child'Constructor; + + procedure Child'Clone (Self : Child; To : in out Child) is + begin' + Root (To) := Root (Self); + Free (To.B); + To.B := new Integer'(Self.B.all); + end Child'Clone; + + procedure Child'Adjust (Self : in out Child; From : Root) is + begin + if From not in Child'Class then + -- This was a partial assignment, fix the A / B consistency + Self.B.all := Self.A.all; + end if; + end Child'Adjust; + +When reasoning about this interface, it's useful to keep in mind that it has +a fundamental design flaw - it allows the user to modify the values of A and +B while possibly leaking the values. A more realistic example would make these +values private, or maybe not automatically allocate objects (but that would +prevent to showcase some aspects of the proposal later). + +Generally speaking, this proposal is providing to the user the tools to develop +a type which will remain safe and consistent, to the contrary of the previous +model that offers shortcuts breaking this ability. + +Simple Copy Assignments +----------------------- + +The simple copy assignment of two objects leads to a sequence of calls to clone +and adjust: + +.. code-block:: ada + + R1, R2 : Root; + + begin + + R2 := R1; + -- Root'Clone (R1, R2); -- Static call + -- Root'Adjust (R2, R1); -- Dispatching call on R2 + +Partial Copy Assignments +------------------------ + +Ada dynamically checks for tags compatibility in the context of two 'Class +types, which can only be assigned if there are of the same type. However, if the +views are definite, the assignment is partial. For example: + +.. code-block:: ada + + R1 : Root; + C1 : Child; + + begin + + Root (C1) := R1; + -- Root'Clone (R1, C1); + -- Child'Adjust (C1, R1); + +In this case, the sequence is exactly the same as before. A similar +thing can be observed in parameters: + +.. code-block:: ada + + procedure Something (A, B : Root) is + begin + A := B; + -- Root'Clone (B, A); + -- Root'Adjust (A, B); + end Something; + + R1 : Root; + C1 : Child; + + begin + + Something (C1, R1); + +In this version of Ada, calls to primitive always dispatch. So the call to +Root'Adjust does dispatch to Child'Adjust. + +Note also that while Adjust dispatches, Clone is a static call, in order to +respect the user choice to assign only the components of the view. For example: + +.. code-block:: ada + + C1 : Child; + C2 : Child; + + begin + + Root (C1) := Root (C2); + -- Root'Clone (C2, C1); -- this is static, only copy Root fields + -- Root'Adjust (C1, C2); -- this dispatches + +Class-Wide Assignments +---------------------- + +Class wide assignments lead to dispatching calls to 'Clone and 'Adjust, ensuring +that the whole object is copied. They also require the two tags to be equals, +like today in Ada. Specifically: + +.. code-block:: ada + + procedure P (V, W : R'Class) is + begin + V := W; + -- if V'Tag = W'Tag then + -- Root'Clone (W, V); -- this dispatches + -- Root'Adjust (V, W); -- this dispatches + -- else + -- raise ; + -- end if; + +Aggregate Assignments +--------------------- + +Aggregates will lead to field by field assignment of a temporary object, +followed by the same sequence of Clone and Adjust. Aggregate objects need to +have a default constructor as this is what's going to be used to create the +temporary object initially: + +.. code-block:: ada + + C : Child; + + begin + + C := (new Integer, new Integer); + -- Tmp : Child; + -- Child'Constructor (Tmp); + -- Tmp.A := new Integer; + -- Tmp.B := new Integer; + -- Child'Clone (Tmp, C); + -- Child'Adjust (C, Tmp); + -- Child'Destructor (Tmp); + +Note that the compiler is free to optimize the above by directly assigning A and +B if it knows that there's no clone and adjust user attributes: + +.. code-block:: ada + + C : Some_Other_Child_With_No_Attributes; + + begin + + C := (new Integer, new Integer); + -- C.A := new Integer; + -- C.B := new Integer; + +The above works the same in the case of a by extension aggregate if the parent +type is directly referred to. Values taken from the parent object are those +resulting of the constructor call: + +.. code-block:: ada + + C : Child; + + begin + + C := (Root with new Integer); + -- Tmp : Child; + -- Child'Constructor (Tmp); + -- Tmp.B := new Integer; + -- Child'Clone (Tmp, C); + -- Child'Adjust (C, Tmp); + -- Child'Destructor (Tmp); + +A few notes on the above sequences: + +- The call to Clone is important, as it allows to clean the target object if + necessary prior to copy. +- Before cloning Tmp we are cloning an object, we need to ensure its own + internal consistency and lifecycle, hence the need to call its constructor and + destructor. +- Usage of aggregate in conjunction with types that provide constructors, + destructor, adjust and clone attributes is somewhat heavy, as the aggregate + needs to be fully initialized before cloned, then reclaimed. It's important + to have self consistency here. However, developer may prefer to reserve + aggregate notation for types that do not require these constructions, and + the compiler should optimize the sequencing in these cases. + +Aggregate Assignments with Extension Copies +------------------------------------------- + +Aggregate by extension that are extending a value as opposed to a default value +require an initial cloning of said value, e.g.: + +.. code-block:: ada + + R : Root; + C : Child; + + begin + + C := (R with new Integer); + -- Tmp : Child; + -- Child'Constructor (Tmp); + -- Root'Clone (R, Tmp); + -- Tmp.B := new Integer; + -- Child'Clone (Tmp, C); + -- Child'Adjust (C, Tmp); + -- Child'Destructor (Tmp); + +Delta Aggregates +---------------- + +Delta aggregates create their initial value from a by-copy constructor: + +.. code-block:: ada + + C1 : Child; + C2 : Child; + + begin + + C2 := (C1 with delta B => new Integer); + -- Tmp : Child := C1; + -- Child'Constructor (Tmp, C1); + -- Tmp.B := new Integer; + -- Child'Clone (Tmp, C); + -- Child'Adjust (C, Tmp); + -- Child'Destructor (Tmp); + +Aggregates with Private Parts or Default Values +----------------------------------------------- + +Aggregates may be provided with default values through the `=> <>` notation. In +that case, the value taken is the one set after call to the parameterless +constructor, e.g.: + +.. code-block:: ada + + C : Child; + + begin + + C := (A => new Integer, others => <>); + -- Tmp : Child; + -- Child'Constructor (Tmp); + -- Tmp.A := new Integer; + -- Child'Clone (Tmp, C); + -- Child'Adjust (C, Tmp); + -- Child'Destructor (Tmp); + +A new syntax in Flare allows types to have both public and private components, +if a user does not have visibility over all the components of a type, he +needs to specify in the aggregate that these non visible values are not +specified with a "private" part at the end of the aggregate, e.g.: + +.. code-block:: ada + + package P is + type Root is tagged record + A, B : Integer; + end record with private; + + R : Root := (1, 2, private); + private + type Root is tagged record + C, D : Integer; + end record; + end P; + +The behavior of a private part is the same as the one of default values. The +presence of this private word is mandatory if the user doesn't have full +visibility of the components of a type, forbidden otherwise. This is different +from the "others => <>" notation which expresses the desire to not value other +otherwise visible components. + +Self Assignment +--------------- + +Detection against self assignment is now mandatory, to avoid users to manually +verify it and possibly making mistakes. The compiler is able to optimize self +assignment checks when it is statically known that the two objects are different +(for example, two local variables without address clauses). So the expansion +provided so far is conceptually a shortcut to: + +.. code-block:: ada + + R1 : Root; + R2 : Root; + begin + R1 := R2; + -- if R1'Address /= R2'Address then + -- Root'Clone (R2, R1); + -- Root'Adjust (R1, R2); + -- end if; + -- + R1 := R1; + -- if R1'Address /= R1'Address then + -- Root'Clone (R1, R1); + -- Root'Adjust (R1, R1); + -- end if; + +Note that this check was already an implementation permission in former versions +of Ada. + +Aggregates and Initialization +----------------------------- + +In the context of an initialization, aggregates, we're going first to create +a temporary object for the aggregate, and then use copy constructor to pass +its value to the final object: + +.. code-block:: ada + + C : Child := (new Integer, new Integer); + -- Tmp : Child; + -- Child'Constructor (Tmp); + -- Tmp.A := new Integer; + -- Tmp.B := new Integer; + -- Child'Constructor (C, Tmp); + -- Child'Destructor (Tmp); + +Note that we're using a copy constructor here instead of the Clone / Adjust +sequence as there's no initial object to modify here. + +Partial Copy and Initialization +------------------------------- + +Partial copy in the context of a copy constructor is following the same pattern +as other copy constructor calls, e.g.: + + +.. code-block:: ada + + C : Child; + R : Root := Root (Child); + -- Root'Constructor (R, Root (Child)); + +In the context of an aggregate by extension that contains a copy, a call to +Clone is necessary, simlar to assignment of the same form: + +.. code-block:: ada + + R : Root; + C : Child := (R with B => new Integer); + -- Tmp : Child; + -- Child'Constructor (Tmp); + -- Root'Clone (R, Tmp); + -- Tmp.B := new Integer; + -- Child'Constructor (C, Tmp); + -- Child'Destructor (Tmp); + +Aggregate Aspect +---------------- + +The presence of constructors, destructors, clone and adjust attributes may +significantly increase the complexity and footprint of assignment and aggregate +usage. The compile may optimize these sequences if it has enough information, +although it's not always clear if it can. + +It is possible to specify that a type hierarchy cannot provide any of these +attributes, and therefore instruct the compiler to generate much simpler code. +This can be done through the Aggregate_Type aspect: + +.. code-block:: ada + + type Root is tagged record + A : access Integer; + end record with Aggregate_Type; + +This aspect must be positionned on the root of a tagged type hierarchy. +It forbids the introduction of user defined constructors, destructor, clone and +adjust attributes in derivations. All record components of such types must +also be Aggregate_Type types. + +Aggregate_Type types cannot be provided to generic tagged formal parameters, as +the generic instance may extend the type and mistakenly add these attributes +not knowing there are forbidden. However, a generic formal parameter may allow +such types by adding the Aggregate_Type aspect in its definition: + +.. code-block:: ada + + generic + type Root is tagged private with Type_Aggregate; + package P + + type Child is new Root with null record; + + procedure Child'Constructor (Self : Child); -- Illegal + +If the compiler is using a generic expansion model, it is free to optimize code +if the actual is indeed a Type_Aggregate type, and generate the full sequences +in other cases. + +Controlled Types +---------------- + +Controlled types, which includes types derived from Ada.Finalization and types +that are using the Finalizable aspect, are incompatible with constructors, +destructors as well as clone and adjust attributes. + +Reference-level explanation +=========================== + +TBD + +Rationale and alternatives +========================== + +The current Ada Finalize / Adjust sequence could be an alternative. However, it +doesn't provide sufficient ability to control consistency of the objects. It +forces the target object to be finalized, it never allows to look at both the +source and target value in the same sequence of statement (finalize on the +previous value, adjust on the new value) and it doesn't allow to control +what is copied. On top of that, when doing assignment on partial objects, +Finalize and Adjust are never dispatched to the real value, leaving potential +inconsistencies. + +Another approach would have been to introduce some kind of a new assignment +overload similar to C++, for example: + + +.. code-block:: ada + + type Root is tagged record + A : access Integer; + end record; + + procedure ":=" (Self : in out Root; From : in out Root); + + type Child is new Root with record + B : access Integer; + end record; + + procedure ":=" (Self : in out Child; From : in out Child); + +However, this still doesn't allow control over partial assignment. There's no +simple way to write: + +.. code-block:: ada + + C1 : Child; + C2 : Child; + begin + Root (C1) := Root (C2); + +And ensure that indeed Root is copied (you'd want to call := on Root) but that +the actual object Child maintains consistency (you'd want to call := on Child). + +We looked at various ways to remove the need of temporaries, for example by +introducing special constructors taking aggregate values as paramters. However, +this quickly leads to the need of creating a lot of extra attributes for all +situations. In light of the added complexity, and the fact that we can +provide means to acheive desired optimization when needed, it didn't look like +the right trade-off. + +Drawbacks +========= + +Prior art +========= + +Unresolved questions +==================== + +Future possibilities +==================== + +The introduction of borrow-checker capabililites as well as move semantics could +allow to optimize more cases. The various temporaries introduced in the +expansion are short lived and could be moved instead of copied, saving one +copy and one destructor operation. From 8a2feb28852e45507dde8a95835d1ae7f39a3a01 Mon Sep 17 00:00:00 2001 From: Quentin Ochem Date: Fri, 12 Sep 2025 10:37:48 -0400 Subject: [PATCH 4/5] fixed typos --- features/rfc-oop-constructors.rst | 64 +++++++++++++++++-------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/features/rfc-oop-constructors.rst b/features/rfc-oop-constructors.rst index 6ed7029..48b990c 100644 --- a/features/rfc-oop-constructors.rst +++ b/features/rfc-oop-constructors.rst @@ -145,7 +145,7 @@ Note that by-copy constructor are also called in assignments situations begin V1 := V2; -- calls destructor on V1, then copy from V2. -A non-limited type always have a by constructor copy available, overloaded or +A non-limited type always has a by-copy constructor available, overloaded or not. Super Constructor Call @@ -188,8 +188,8 @@ Note that the constructor of an abstract type can be called here, for example: null; end Child'Constructor; -When valuating values in the Super aspect, the constructed object does not -exit yet. It is illegal to refer to this parameter in the aspect. +When valuating values in the Super aspect, the object under construction does +not exit yet. It is illegal to refer to its parameter in the aspect. Initialization Lists -------------------- @@ -210,7 +210,7 @@ Initialization of components can be done in two ways: - Through an ``Initialize`` aspect that can rely on constructor parameters. If the component is of a type that doesn't have a parameterless constructor, it -has to be initialized by on of these two mechanism. +has to be initialized by one of these two mechanism. Here's an example of using ``Initialize`` for such a case: @@ -280,7 +280,7 @@ initialized as described at declaration time. For example: end C'Constructor; V1 : C := C'Make; -- Will print A FROM RECORD, B FROM RECORD - V2 : C := C'Make ("ATERNATE A"); -- Will print ATERNATE A, B FROM RECORD + V2 : C := C'Make ("ALTERNATE A"); -- Will print ALTERNATE A, B FROM RECORD Note for implementers - the objective of the semantic above is to make initialization as efficient as possible and to avoid undecessary processing. @@ -322,7 +322,7 @@ object. The following for example will issue an error: end record; type Child is new Root with record - C : R; + C : Integer; end record; procedure Child'Constructor (Self : in out Child); @@ -338,9 +338,9 @@ object. The following for example will issue an error: null; end Child'Constructor; -When valuating values in the Initialize aspect, the constructed object does not -exit yet. It is illegal to refer to this parameter in the aspect. The following -is illegal: +When valuating values in the Initialize aspect, the object under construction +does not exist yet. It is illegal to refer to this parameter in the aspect. +The following is illegal: .. code-block:: ada @@ -413,7 +413,7 @@ constructors, the parent type discriminants are not set. For example: type Root (V : Integer) is tagged null record; - procedure Root'Constructor (Self : in out Child); + procedure Root'Constructor (Self : in out Root); -- note that we're not specifying Root discriminant as Root has a constructor type Child is new Root with null record; @@ -523,7 +523,7 @@ such subtyping can also be used for components: end record; In this version of the proposal, discriminant subtyping is only legal for -untagged types. Considerations around type types are described in the future +untagged types. Considerations around tagged types are described in the future possibilities section. Constructors and Type Predicates @@ -578,32 +578,40 @@ constructors are declared. Notably: - Requirement on parameterless and by copy constructors can be removed by marking them abstract. -As for subsprograms, generic formal constructors are introduced with the `with` +As for subprograms, generic formal constructors are introduced with the `with` reserved word. For example: .. code-block:: ada generic type T1 is tagged private; - -- Needs at least a parameterless and a by-copy constructor + -- Needs at least a parameterless and a by-copy constructor, + -- if T1 is by constructor. type T2 is tagged private; with T2'Constructor (Self : in out T2; V : Integer); -- No parameterless constructor expected, but a by-copy one type T3 is tagged private; - with T2'Constructor (Self : in out T2) is abstract; + with T3'Constructor (Self : in out T3) is abstract; -- No parameterless constructor expected, but a by-copy one type T4 is tagged private; - with T4'Constructor (Self : in out T2) is abstract; - with T4'Constructor (Self : in out T2; Src : T2) is abstract; - -- No parameterless constructor expected, but a by-copy one + with T4'Constructor (Self : in out T4) is abstract; + with T4'Constructor (Self : in out T4; Src : T4) is abstract; + -- Neither parameterless nor by-copy constructor expected package G is - V : T1; -- OK, we have parameterless constructor - V2 : T1 := V; -- OK, we have by-copy constructor + V11: T1; -- OK, we have parameterless constructor for T1 + V12: T1 := V11; -- OK, we have by-copy constructor for T1 + + V21: T2; -- NOK, no parameterless constructor expected for T2 + V22: T2 := V21; -- OK, we have by-copy constructor for T2 - V3 : T4; -- NOK we don't have parameterless constructor for T4 + V31: T3; -- NOK, no parameterless constructor expected for T3 + V32: T3 := V31; -- OK, we have by-copy constructor for T3 + + V41: T4; -- NOK we don't have parameterless constructor for T4 + V42: T4 := V41; -- NOK we don't have by-copy constructor for T4 end G; package P is @@ -625,9 +633,9 @@ reserved word. For example: -- All of these are OK, T1 provides all the necessary constructors package G2 is new G ( - T1 => R2, -- Error, R2 doesn't have parametelress and by copy constructor - T2 => R2, -- Error, R2 doesn't have parametric and by copy constructor - T3 => R2, -- Error, R2 doesn't by copy constructor + T1 => R2, -- Error, R2 doesn't have parameterless and by-copy constructor + T2 => R2, -- Error, R2 doesn't have parametric and by-copy constructor + T3 => R2, -- Error, R2 doesn't have by-copy constructor T4 => R2 -- OK, no constructor expected here ); @@ -683,7 +691,7 @@ type by a "by constructor" tagged type, e.g.: procedure New_Child'Constructor (Self : in out New_Child; L1, L2 : Integer); -In that case, any child of New_Child has to be a by-constructor type, ie it +In that case, any child of New_Child has to be a by-constructor type, i.e. while it is possible to extend a "regular" tagged type by a "by constructor" tagged type, it is not possible to extend a "by constructor" tagged type by a regular one. @@ -695,7 +703,7 @@ In certain situations, it's important to know if an object is considered initialized. For example, this can clarify wether passing a value of such object may lead to errors. -An object value in a constructor is consisered initialized once the `Super` and +An object value in a constructor is considered initialized once the `Super` and `Initialze` aspects have been computed. Formally, the role of the constructor is to establish further properties than the initialization. @@ -729,7 +737,7 @@ operation. The discriminants may need to be valuated, the super constructor must be called. In some cases, the object memory is already allocated (think of the case of a component with an implicit constructor call). -Having a constructor as a procedure also allows for expansion without undecessary +Having a constructor as a procedure also allows for expansion without unnecessary copies: .. code-block:: ada @@ -893,7 +901,7 @@ Consider the following hierarchy: null; end Root'Constructor; - type Child is new Root with null record with Constructor => Constr; + type Child is new Root with null record; procedure Child'Constructor (Self : in out Bla; C : Boolean) with Super => (C); @@ -901,7 +909,7 @@ Consider the following hierarchy: null; end Child'Constructor; -Child does not have any discrimininant. Root discriminant is set by its own +Child does not have any discriminant. Root discriminant is set by its own constructor. There is currently no syntax allowing to subtype Child and provide a constrain to its discriminant. From f21ffc53b8a722e4ab3746cd731d878a94da6560 Mon Sep 17 00:00:00 2001 From: Quentin Ochem Date: Fri, 12 Sep 2025 13:15:38 -0400 Subject: [PATCH 5/5] additional typo fixes --- .../rfc-oop-aggregates-and-assignments.rst | 20 +++++++++---------- features/rfc-oop-destructors.rst | 3 ++- features/rfc-oop-dispatching.rst | 6 +++--- features/rfc-oop-primitives.rst | 16 +++++++-------- features/rfc-oop-super.rst | 2 +- 5 files changed, 24 insertions(+), 23 deletions(-) diff --git a/features/rfc-oop-aggregates-and-assignments.rst b/features/rfc-oop-aggregates-and-assignments.rst index e45d575..85d5832 100644 --- a/features/rfc-oop-aggregates-and-assignments.rst +++ b/features/rfc-oop-aggregates-and-assignments.rst @@ -22,7 +22,7 @@ replace initialization. But the consequence is that there's no way to ensure that a given sequence of statement is putting an object in a consistent state at creation time (unlike traditional constructors). -Second Adjust perform a post-copy update to a type. This causes a double issue, +Second, Adjust perform a post-copy update to a type. This causes a double issue, first in terms of performance, as assignment may not need all components to be modified. But this also limits the control over assignment logic, as the user has no way to know what was the initial state of the object or what object @@ -30,23 +30,23 @@ was initially copied from. Third, Ada allows partial assignment of objects through parent views. To some respect, this is also an issue, as the resulting object may be inconsistent, -with only part updated, and potentially now way in Adjust to understand which +with only part updated, and potentially no way in Adjust to understand which part was changed and which part was not. A related issue is the so called "Aggregate by extension" where a root object is copied into a child one with specific values provided by the aggregate, again here with no control over the consistency of values (not even in Adjust -in the case of initialiation). +in the case of initialization). To solve these issues, we propose to introduce a two step object update mechanism through a value duplication ('Clone) and post update adjustment ('Adjust). Note that this extra complexity is driven from the desire to support natively -Ada constructions (aggregates, partial copie, etc) and improve compatibility +Ada constructs (aggregates, partial copies, etc) and improve compatibility between classes and tagged types. Users can leverage default implementation if such level of control is unecessary. Some language extension may also allow -to forbig aggregates and partial update on specific types (although this +to forbid aggregates and partial update on specific types (although this introduces complexities in generics that now need to specify wether these restricted types are allowed or not). @@ -92,7 +92,7 @@ calls clone operation of all the components one by one. The compiler is free to optimize to bitwise copies if clone operations are not user-defined. Calls to 'Clone are statically resolved when used on definite views, and -dynamcially resolved on 'Class wide type. This is arguably a departure from the +dynamically resolved on 'Class wide type. This is arguably a departure from the "all calls are dispatching" requirement from other aspects of the OOP design, but is required to allow partial copies of objects which are done today in various places Ada. @@ -108,7 +108,7 @@ different from the legacy Ada Adjust primitive in that it has an argument refering to the initial value. Note that the From parameter of adjust is always typed after the root type of the tagged record hierarchy - indeed, the source object may be higher up in the derivation chain in the case of partial -copy. This value value is provided for reference but is not expected to be +copy. This value is provided for reference but is not expected to be modified. .. code-block:: ada @@ -214,7 +214,7 @@ Partial Copy Assignments ------------------------ Ada dynamically checks for tags compatibility in the context of two 'Class -types, which can only be assigned if there are of the same type. However, if the +types, which can only be assigned if they are of the same type. However, if the views are definite, the assignment is partial. For example: .. code-block:: ada @@ -348,7 +348,7 @@ A few notes on the above sequences: destructor, adjust and clone attributes is somewhat heavy, as the aggregate needs to be fully initialized before cloned, then reclaimed. It's important to have self consistency here. However, developer may prefer to reserve - aggregate notation for types that do not require these constructions, and + aggregate notation for types that do not require these constructs, and the compiler should optimize the sequencing in these cases. Aggregate Assignments with Extension Copies @@ -616,7 +616,7 @@ We looked at various ways to remove the need of temporaries, for example by introducing special constructors taking aggregate values as paramters. However, this quickly leads to the need of creating a lot of extra attributes for all situations. In light of the added complexity, and the fact that we can -provide means to acheive desired optimization when needed, it didn't look like +provide means to achieve desired optimization when needed, it didn't look like the right trade-off. Drawbacks diff --git a/features/rfc-oop-destructors.rst b/features/rfc-oop-destructors.rst index bd72eaf..9d33233 100644 --- a/features/rfc-oop-destructors.rst +++ b/features/rfc-oop-destructors.rst @@ -76,7 +76,8 @@ The destruction sequence works in the following way: - If a type has an explicit destructor, it is first called. - If a type has components hierarchy, wether or not it has an explicit - destructor, the destructor sequence is called on each components. + destructor, the destructor sequence is called on each components, in + reverse order of construction. - If a type is in a tagged hierarchy, wether or not it has an explicit destructor, the parent destructor sequence is called. diff --git a/features/rfc-oop-dispatching.rst b/features/rfc-oop-dispatching.rst index 25ec214..b2e892e 100644 --- a/features/rfc-oop-dispatching.rst +++ b/features/rfc-oop-dispatching.rst @@ -132,7 +132,7 @@ does not support multi-parameter dispatching. Dispactching on Returned Types ------------------------------ -A tag indeterminate disatching call is illegal (as it is the case today). For +A tag indeterminate dispatching call is illegal (as it is the case today). For example: .. code-block:: ada @@ -198,7 +198,7 @@ a scope where Default_Dispatching_Calls is On. For example: [... some code ...] A_D.all (Obj); -- This dispatches - A_ND.all (Obj); -- This doesn't dispatches + A_ND.all (Obj); -- This doesn't dispatch [... some code ...] @@ -221,7 +221,7 @@ Rationale and alternatives It is a potential vulnerability not to call an overriden primitive. This may lead to an object to be in an state that has not been anticipated, in particular when the role of the overriden primitive is to keep the state of the derived -object consistant. It's also commonly the case in most OOP languages that +object consistent. It's also commonly the case in most OOP languages that dispatching is the default expected behavior and non dispatching the exception. This also fixes a common confusion in Ada, where the dispatching parameter of A diff --git a/features/rfc-oop-primitives.rst b/features/rfc-oop-primitives.rst index 61df3da..19d6a7b 100644 --- a/features/rfc-oop-primitives.rst +++ b/features/rfc-oop-primitives.rst @@ -14,13 +14,13 @@ Guide-level explanation Class objects ------------- -A new type of type is introduce, the `class` type. This type operates similarly +A new type of type is introduced, the `class` type. This type operates similarly to tagged type (there are even situations where it can be derived from one) with a number of differences: - It's always a by-constructor type - It cannot have coextensions (so no access-type discriminants) -- It primitives must follow the rules of First_Controlling_Parameter +- Its primitives must follow the rules of First_Controlling_Parameter - It always follows the rules of Default_Dispatching_Calls - Primitives need to be declared within its scope - operations declared outside of its scope are not primitives. @@ -55,7 +55,7 @@ bodies. The following demonstrates the above: procedure Prim (Self : in out R; V : Integer) is begin Self.F := V; - end record; + end Prim; procedure Prim_2 (Self : in out R; V : Integer) is begin @@ -72,7 +72,7 @@ bodies. The following demonstrates the above: Primitives declared within a type can only be called via prefix notation. When primitives are declared in a scope, there can no longer be primitives declared -ouside of the scope, such declarations are non-primitives. +outside of the scope, such declarations are non-primitives. Scoped primitives can be referred to with their fully qualified notation (for example, when using access to suprograms or renamings), for example here as @@ -129,8 +129,8 @@ Operators can be declared as primitives: end record; type T2 is new T1 with class record - procedure "=" (Left : T2; Right : T1); - function "+" (Left : T2, Right : T1) return T1; + function "=" (Left : T2; Right : T1) return Boolean; + function "+" (Left : T2; Right : T1) return T1; end record; end P; @@ -165,7 +165,7 @@ It is possible to also scope primitives in regular records: end P; -Declaring primities outside of regular records is still possible. It's not +Declaring primitives outside of regular records is still possible. It's not possible to declare primitives within a regular tagged record. Non-primitive scoped operations @@ -221,7 +221,7 @@ of the scope of a class record cannot be called though prefix notation. Notably: Discriminants ------------- -Disciminants of class record need to be repeated on both public and private +Discriminants of class record need to be repeated on both public and private declaration views, but not their body view (similar to e.g. protected types). E.g: diff --git a/features/rfc-oop-super.rst b/features/rfc-oop-super.rst index c952425..267e306 100644 --- a/features/rfc-oop-super.rst +++ b/features/rfc-oop-super.rst @@ -34,7 +34,7 @@ can be used to make non dispatching calls. For example: end Call; Note that `'Super` being used to make non dispatching calls to primitives using -the parent view, it is only available is said view is of a derived type (there'S +the parent view, it is only available if said view is of a derived type (there'S no primitive to call otherwise). Reference-level explanation