Version 1.1 of ai12s/ai12-0412-1.txt

Unformatted version of ai12s/ai12-0412-1.txt version 1.1
Other versions for file ai12s/ai12-0412-1.txt

!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.)

****************************************************************

Questions? Ask the ACAA Technical Agent