Version 1.1 of 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