!standard 3.4(18/2) 09-11-30 AI05-0164-1/02 !standard 6.1(27.1/2) !standard 6.1(28.2/2) !class binding interpretation 09-10-21 !status Amendment 201Z 09-11-30 !status ARG Approved 10-0-1 09-11-08 !status work item 09-10-21 !status received 09-06-23 !priority Low !difficulty Medium !qualifier Omission !subject Parameters of access-to-subprogram parameters and derivation !summary The types of parameters of access-to-subprogram parameters are not modified by derivation. !question When a derived type is declared, 3.4(18) says that the profiles of inherited subprograms are obtained by systematically replacing each "subtype of its profile" that is of the parent type, with the derived type (actually the "corresponding subtype"). 6.1(27.1/2) says that for access parameters of an access-subprogram type, the subtypes of the profile include the subtypes of the profile of the parameter type. So this means that if Typ2 is derived from Typ1, if any primitive operations of Typ1 have access-subprogram parameters (or results) that themselves have parameters of type Typ1 (or have access-subprogram parameters that have parameters of type Typ1, and so on recursively), those occurrences of Typ1 are changed to Typ2 in the inherited subprogram declaration. I.e.: type Typ1 is record ... end record; procedure Primitive_Op (X : in out Typ1; Func : access function (Z : Typ1) return Typ1); type Typ2 is new Typ1; -- Implicit inherited subporgram: --procedure Primitive_Op (X : in out Typ2; -- Func : access function (Z : Typ2) -- return Typ2); But this causes a number of problems. For instance, if Typ2 is constrained and Typ1 is not, the body of Primitive_Op won't have code to check the constraint before the value is returned. It's also possible that Typ1 and Typ2 generate different code for parameter passing (for instance, if Typ1 is an unconstrained discriminanted record with defaults, it may have a 'Constrained value passed with the parameter, while a constrained Typ2 may not have such a value passed. Finally, if a dispatching call is made to Primitive_Op, a non-dispatching 'Access value will be passed, which very easily could have the wrong types for the actual body called. Thus, it seems that derivation should not change the profile of access-to-subprogram parameters. Is this the intent? (Yes.) !recommendation (See Summary.) !wording Modify 3.4(18/2): The profile of an inherited subprogram (including an inherited enumeration literal) is obtained from the profile of the corresponding (user-defined) primitive subprogram of the parent or progenitor type, after systematic replacement of each subtype of its profile (see 6.1){, other than those found in the designated profile of an access_definition,} that is of the parent or progenitor type with a *corresponding subtype* of the derived type. For a given subtype of the parent or progenitor type, the corresponding subtype of the derived type is defined as follows: Modify 6.1(27.1/2): * For any access parameters of an access-to-subprogram type, the subtypes of the {designated} profile of the parameter type. Modify 6.1(28.2/2): * For any access result type of an access-to-subprogram type, the subtypes of the {designated} profile of the result type. !discussion Changing the definition of the profile of a subprogram is not possible; it's used in the definition of conformance and specifically by the definition of subtype conformance. The subtypes of access-to-subprogram parameters have to participate in conformance. Thus we add an exception to 3.4(18). --- The changes to 6.1(27.1/2) and 6.1(28.2/2) are needed as access-to-subprogram types have designated profiles, but don't directly have a profile. !corrigendum 3.4(18/2) @drepl The profile of an inherited subprogram (including an inherited enumeration literal) is obtained from the profile of the corresponding (user-defined) primitive subprogram of the parent or progenitor type, after systematic replacement of each subtype of its profile (see 6.1) that is of the parent or progenitor type with a @i of the derived type. For a given subtype of the parent or progenitor type, the corresponding subtype of the derived type is defined as follows: @dby The profile of an inherited subprogram (including an inherited enumeration literal) is obtained from the profile of the corresponding (user-defined) primitive subprogram of the parent or progenitor type, after systematic replacement of each subtype of its profile (see 6.1), other than those found in the designated profile of an @fa, that is of the parent or progenitor type with a @i of the derived type. For a given subtype of the parent or progenitor type, the corresponding subtype of the derived type is defined as follows: !corrigendum 6.1(27.1/2) @drepl @xbullet @dby @xbullet !corrigendum 6.1(28.2/2) @drepl @xbullet @dby @xbullet !ACATS Test Add a B-Test (using overriding indicators) to ensure that routines are inherited properly. !appendix !topic Dispatching operations and access-subprogram parameters !reference 3.9.2(16), 6.1(27.1/2), 3.4(18/2) !from Adam Beneschan 09-06-23 !discussion When a derived type is declared, 3.4(18) says that the profiles of inherited subprograms are obtained by systematically replacing each "subtype of its profile" that is of the parent type, with the derived type (actually the "corresponding subtype"). 6.1(27) says that for access parameters of an access-subprogram type, the subtypes of the profile include the subtypes of the profile of the parameter type. (I think that this clause should read "subtypes of the designated profile of the parameter type", since access types don't themselves have profiles by 6.1(22); I'm assuming that's what is meant.) So this appears to mean that if Typ2 is derived from Typ1, if any primitive operations of Typ1 have access-subprogram parameters (or result) that themselves have parameters of type Typ1 (or have access-subprogram parameters that have parameters of type Typ1, and so on recursively), those occurrences of Typ1 are changed to Typ2 in the inherited subprogram declaration. I.e.: type Typ1 is record ... end record; procedure Primitive_Op (X : in out Typ1; Func : access function (Z : Typ1) return Typ1); type Typ2 is new Typ1; -- Implicit inherited subporgram: --procedure Primitive_Op (X : in out Typ2; -- Func : access function (Z : Typ2) -- return Typ2); This would seem to cause a problem with dispatching operations, though. 3.9.2(16) says that when a call on a dispatching operation has a dynamically tagged controlling tag, and there is more than one controlling operand, they must all have the same tag or else Constraint_Error is raised. But an access-subprogram parameter is not a controlling operand, so there appears to be no check at all to make sure it's a subprogram with the right type. So what does this do? package Pack1 is type Root is tagged null record; procedure Op (Obj : in out Root; Count : in Integer; Func : access function (Obj : in Root) return Root); end Pack1; package body Pack1 is procedure Op (Obj : in out Root; Count : in Integer; Func : access function (Obj : in Root) return Root) is begin null; end Op; end Pack1; with Pack1; package Pack2 is type T1 is new Pack1.Root with record F1 : Integer := 0; end record; overriding procedure Op (Obj : in out T1; Count : in Integer; Func : access function (Obj : in T1) return T1); end Pack2; with Text_IO; package body Pack2 is procedure Op (Obj : in out T1; Count : in Integer; Func : access function (Obj : in T1) return T1) is begin for I in 1 .. Count loop Obj := Func(Obj); Text_IO.Put_Line (Integer'Image (Obj.F1)); end loop; end Op; end Pack2; with Pack1, Pack2; procedure Test1 is function Func1 (Obj : in Pack1.Root) return Pack1.Root is begin return Obj; end Func1; procedure Dispatch (Obj : in out Pack1.Root'Class; Count : in Integer) is begin Pack1.Op (Obj, Count, Func1'access); --dispatching call end Dispatch; X2 : Pack2.T1; begin Dispatch (X2, 5); end Test1; Since the dispatching call doesn't make sure (at runtime) that Func1 has a parameter type that matches Obj's tag, it looks like the call is executed, and the overriding procedure Pack2.Op is called, and that procedure thinks it's calling a function that returns something of type T1 when it isn't. This cannot be good. **************************************************************** From: Christoph Grein Sent: Wednesday, June 24, 2009 2:37 AM overriding procedure Op (Obj : in out T1; Count : in Integer; Func : access function (Obj : in T1) return T1); GNAT Pro 6.2.1 says: subprogram Op is not overriding. This is accepted as overriding, but it leads of course to problems in the body: procedure Op (Obj : in out T1; Count: in Integer; Func : access function (Obj: in Pack_0.Root) return Pack_0.Root); **************************************************************** From: Adam Beneschan Sent: Wednesday, June 24, 2009 9:40 AM > overriding > procedure Op (Obj : in out T1; > Count : in Integer; > Func : access function (Obj : in T1) return T1); > > GNAT Pro 6.2.1 says: subprogram Op is not overriding. I'm not surprised. I'm guessing that T1 is derived from Root, and GNAT implicitly declares the subprogram inherited from this one: procedure Op (Obj : in out Root; Count : in Integer; Func : access function (Obj : in Root) return Root); it replaces "Root" by "T1" in the profile, but when doing so, it fails to go into the profile of the access-subprogram parameter, resulting in this inherited subprogram: procedure Op (Obj : in out T1; Count : in Integer; Func : access function (Obj : in Root) return Root); So then of course it would reject the above declaration as "overriding" (and accept this one:) > This is accepted as overriding, but it leads of course to problems in > the body: > > procedure Op (Obj : in out T1; > Count: in Integer; > Func : access function (Obj: in Pack_0.Root) return > Pack_0.Root); Our compiler has the same behavior. However, I believe GNAT (like our compiler) is doing the wrong thing, based on the wording of the RM. It could be that the wording of the RM doesn't reflect what is intended, and that "systematically replacing subtypes of the profile" isn't supposed to include subtypes in an access-subprogram-parameter profile. If that's the case, the RM needs to be changed, and then GNAT will be right without making any changes. But as it currently stands, unless there is some other applicable rule I missed, the compilers are not following the RM. ****************************************************************