!standard 6.1.1(18.2/4) 20-12-03 AI12-0412-1/01 !class binding interpretation 20-12-03 !status work item 20-12-03 !status received 20-11-23 !priority Low !difficulty Easy !qualifier Error !subject Pre'Class using an abstract function applied to a concrete operation of abstract type !summary Allow specifying a Pre'Class or Post'Class aspect for a concrete primitive of an abstract type even if it uses an abstract function, but then disallow nondispatching calls on such a primitive. !question Given the following example: package Object is type T is abstract tagged record B : Boolean := True; end record; function Is_Valid (X : T) return Boolean is abstract; procedure Do_Stuff (X : in out T) is null with Extensions_Visible, Pre'Class => Is_Valid (X); end Object; --- Currently the above example is illegal, because the "corresponding expression" for the given Pre'Class aspect, when constructed according to the rules of 6.1.1(18/5-18.2/4), would be illegal, since it would involve a call on an abstract function Is_Valid. But the above combination of a null concrete primitive and an abstract Pre'Class would seem quite reasonable. Should the above be legal? (Yes) Note that if this is made legal, then a non dispatching call on the concrete primitive with such a Pre'Class should be caught: Y : T'Class := ... begin Do_Stuff (T(Y)); The above call would need to be be illegal, given that we are making a non-dispatching call on Do_Stuff, and it has a Pre'Class that is abstract for the type T. !recommendation Using an abstract function in a Pre'Class aspect for an operation of an abstract type is a common pattern. It is inevitable for interface types (since all primitive functions of an interface type are necessarily abstract -- no "null" functions are allowed), and will be common for any abstract root type. It will also be common to have null defaults for primitives of an abstract type, if they represent a kind of "hook" which is expected to often be a no-op. Using these two together, namely a null default and an abstract Pre'Class, should be allowed. As mentioned above, the above combination of features is disallowed by 6.1.1(18/5-18.2/4). We propose to allow it, because it would be unfortunate to have to provide a concrete Is_Valid for such an abstract type, as then you would lose the protection against forgetting to override Is_Valid in a concrete descendant. But because it is possible to actually invoke a concrete primitive of an abstract type by an explicit conversion to the abstract type, we need to catch this problem somewhere. We therefore propose to require that it be caught at the point of such a call. Also, we recommend that it be illegal even if precondition checks are disabled, as we don't want legality to be affected by the state of an assertion policy. Revise 6.1.1(18.2/4): The primitive subprogram S is illegal if [it]{the given descendant of T} is not abstract and the corresponding expression for a Pre'Class or Post'Class aspect would be illegal. {If S itself is not abstract, then a non dispatching call on S is illegal if the corresponding expression for a Pre'Class or Post'Class aspect would be illegal.} !discussion Most of the rationale is given in the !recommendation. To minimize disruption, we have chosen to keep the changes as part of the existing Static Semantics paragraph (18.2/4), rather than moving them into the Legality Rules paragraphs. !ASIS No ASIS effect. !ACATS test ACATS B & C-Tests are needed to check the newly allowed cases, and to check that a non-dispatching call of such a routine is illegal. !appendix From: Tucker Taft Sent: Monday, November 23, 2020 8:39 AM !Subject Pre'Class using an abstract function applied to a concrete operation of abstract type !summary Allow specifying a Pre'Class or Post'Class aspect for a concrete primitive of an abstract type even if it uses an abstract function, but then disallow nondispatching calls on such a primitive. !problem Given the following example: package Object is type T is abstract tagged record B : Boolean := True; end record; function Is_Valid (X : T) return Boolean is abstract; procedure Do_Stuff (X : in out T) is null with Extensions_Visible, Pre'Class => Is_Valid (X); end Object; --- Currently the above example is illegal, because the "corresponding expression" for the given Pre'Class aspect, when constructed according to the rules of 6.1.1(18/5-18.2/4), would be illegal, since it would involve a call on an abstract function Is_Valid. But the above combination of a null concrete primitive and an abstract Pre'Class would seem quite reasonable. Therefore, we suggest that it should be legal, but then a non dispatching call on the concrete primitive with such a Pre'Clss should be caught: Y : T'Class := ... begin Do_Stuff (T(Y)); The above call would need to be be illegal, given that we are making a non-dispatching call on Do_Stuff, and it has a Pre'Class that is abstract for the type T. !proposal Using an abstract function in a Pre'Class aspect for an operation of an abstract type is a common pattern. It is inevitable for interface types (since all primitive functions of an interface type are necessarily abstract -- no "null" functions are allowed), and will be common for any abstract root type. It will also be common to have null defaults for primitives of an abstract type, if they represent a kind of "hook" which is expected to often be a no-op. Using these two together, namely a null default and an abstract Pre'Class, should be allowed. As mentioned above, the above combination of features is disallowed by 6.1.1(18/5-18.2/4). We propose to allow it, because it would be unfortunate to have to provide a concrete Is_Valid for such an abstract type, as then you would lose the protection against forgetting to override Is_Valid in a concrete descendant. But because it is possible to actually invoke a concrete primitive of an abstract type by an explicit conversion to the abstract type, we need to catch this problem somewhere. We therefore propose to require that it be caught at the point of such a call. Also, we recommend that it be illegal even if precondition checks are disabled, as we don't want legality to be affected by the state of an assertion policy. !wording Revise 6.1.1(18.2/4): The primitive subprogram S is illegal if [it]{the given descendant of T} is not abstract and the corresponding expression for a Pre'Class or Post'Class aspect would be illegal. {If S itself is not abstract, then a non dispatching call on S is illegal if the corresponding expression for a Pre'Class or Post'Class aspect would be illegal.} !discussion Most of the rationale is given in the !proposal. To minimize disruption, we have chosen to keep the changes as part of the existing Static Semantics paragraph (18.2/4), rather than moving them into the Legality Rules paragraphs. **************************************************************** From: Claire Dross Sent: Thursday, December 3, 2020 9:13 AM > The above call would need to be be illegal, given that we are making a > non-dispatching call on Do_Stuff, and it has a Pre'Class that is abstract > for the type T. I am not sure the use case is important enough to warrant a special case. If Do_Stuff is abstract, then it would be legal I suppose. I am not convinced that it brings much to allow Do_Stuff to be concrete if we are not allowed to call it in a non dispatching way. **************************************************************** From: Randy Brukardt Sent: Thursday, December 3, 2020 11:20 PM I originally had the same thought, but then I remembered that interfaces only allow abstract subprograms and null procedures. If you want to define a Pre'Class or Post'Class for such a null procedure, you'd run into this problem as the functions that you'd call are going to be abstract. Tucker probably should have put the reason into his original message; he did mention it in his AI write-up. Remember that a null procedure is essentially a default implementation for an interface. We should have allowed expression functions to be used that way as well (but they hadn't been invented yet). In any case, we don't want to force people to have to chose between contracts or default implementations. Ideally, as many features as possible work together seamlessly. (If you are like me and don't find interfaces very valuable, then it's harder to get interested in this fix. But I don't see much point in making things harder for those that do like these things.) ****************************************************************