Version 1.5 of ai05s/ai05-0254-1.txt
!standard 6.1.1(0) 11-07-24 AI05-0254-1/03
!standard 3.9.2(20.2/2)
!reference AI05-0247-1
!class Amendment 11-06-16
!status Amendment 2012 11-07-22
!status ARG Approved 9-0-3 11-06-25
!status work item 11-06-16
!status received 11-06-05
!priority High
!difficulty Hard
!subject Do we REALLY have contracts right?
!summary
Improve the wording proposed in AI05-0247-1.
!question
The fixes made in AI05-0247-1 have some ARG members uncomfortable.
Should we reconsider? (Only the wording.)
!proposal
(See wording.)
!wording
Modify 3.9.2(20.4/3):
otherwise, the action is the same as the action for the corresponding operation of
the parent type or progenitor type from which the operation was inherited
{(except that additional class-wide postconditions may apply -- see 6.1.1)}.
If there is more than one such corresponding operation, the action is that for
the operation that is not a null procedure, if any; otherwise, the action is
that of an arbitrary one of the operations.
Modify 6.1.1(3/3):
This aspect specifies a class-wide precondition for a callable entity and its
descendants; it shall be specified by an expression, called a class-wide
precondition expression. If not specified for an entity, {then if no other
class-wide precondition applies to the entity,} the class-wide precondition
expression for the entity is the enumeration literal True.
Add a new AARM Ramification after 6.1.1(3/3):
If other class-wide preconditions apply to the entity and no class-wide
precondition is specified, no class-wide precondition is defined for the entity;
of course, the class-wide preconditions (of ancestors) that apply are still going
to be checked. We need subprograms that don't have ancestors and don't specify a
class-wide precondition to have a class-wide precondition of True, so that adding
such a precondition to a descendant has no effect (necessary as a dispatching call
through the root routine would not check any class-wide precondition).
Replace 6.1.1(10-12/3) with:
If a type T has an implicitly declared subprogram P inherited from a parent type T1
and a homograph (see 8.3) of P from a progenitor type T2, and
* the corresponding primitive subprogram P1 of type T1 is neither null nor abstract; and
* the class-wide precondition expression True does not apply to P1 (implicitly or
explicitly); and
* there is a class-wide precondition expression that applies to the corresponding primitive
subprogram P2 of T2 that does not fully conform to any class-wide precondition that applies to P1,
then:
* If the type T is abstract the implicitly declared subprogram P is *abstract*.
* Otherwise, the subprogram P *requires overriding* and shall be overridden with a
nonabstract subprogram.
Modify 6.1.1(23/3):
The {checks are performed in an arbitrary order}[order of performing the checks
is not specified], and if any of the
class-wide precondition expressions evaluate to True, it is not specified
whether the other class-wide precondition expressions are evaluated. {The
precondition checks and any check for elaboration of the subprogram body are
performed in an arbitrary order}[It is not
specified whether any check for elaboration of the subprogram body is
performed before or after the precondition checks]. It is not specified whether
in a call on a protected operation, the checks are performed before or after
starting the protected action. For a task or protected entry call, the checks
are performed prior to checking whether the entry is open.
Modify 6.1.1(24/3):
If the assertion policy in effect at the point of a subprogram or entry
declaration is Check, then upon successful return from a call of the
subprogram or entry, prior to copying back any by-copy in out or out
parameters, the postcondition check is performed. This consists of the
evaluation of the specific and class-wide postcondition expressions that apply
to the subprogram or entry. If any of the postcondition expressions
evaluate to False, then Ada.Assertions.Assertion_Error is raised.
The {postcondition expressions are evaluated in an arbitrary order}[order of
performing the checks is not specified], and if any
postcondition expression evaluates to False, it is not specified whether any other
postcondition expressions are evaluated. {The postcondition check and constraint checks
associated with copying back @b<in out> or @b<out> parameters are performed in an
arbitrary order}[It is not specified whether any constraint checks associated with
copying back in out or out parameters are performed before or after the postcondition
checks].
Replace 6.1.1(26/3) with:
For any subprogram or entry call (including dispatching calls), the checks
that are performed that verify specific precondition expressions and specific
and class-wide postcondition expressions are determined by those for the subprogram
or entry actually invoked. Note that the class-wide postcondition expressions
verified by the postcondition check that is part of a call on a primitive
of type T includes all class-wide postcondition expressions originating
in any progenitor of T [Redundant: , even if the primitive called
is inherited from a type T1 and some of the postcondition expressions do
not apply to the corresponding primitive of T1].
Replace 6.1.1(27/3) with:
The class-wide precondition check for a call to a subprogram or entry consists
solely of checking the class-wide precondition expressions that apply to the denoted
callable entity (not necessarily the one that is invoked).
For a call via an access-to-subprogram value, all precondition and postcondition checks
performed are determined by the subprogram or entry denoted by the prefix of the Access
attribute reference that produced the value.
!discussion
The broad changes made in AI05-0247-1 are necessary to preserve LSP, to
allow programmers and compilers to trust contracts, and to allow static
analyzability of dispatching calls. There really aren't any other solutions
that have all of these properties.
Specifically, the way class-wide preconditions and postconditions are
combined is required by LSP. Separating specific preconditions into a
separate check eliminates counter-intuitive semantics without (directly)
violating LSP.
Having the class-wide precondition check on a dispatching call check the
preconditions known to the call rather than those known to the body allows
static analysis of the contracts of such calls, has no effect on correctness
(as the body must have a weaker precondition), and makes more obvious to the
programmer of such a call what the class-wide precondition is (so they can
meet it).
But most of the effort of AI05-0247-1 was spent on the details of inherited
routines that have additional contracts added (via interfaces).
It eventually was decided to make such preconditions illegal (the routine
requires overriding), and require the postconditions to be evaluated as-if
there is an overriding body. The precondition case is a problem as it either
requires violating LSP or calling a routine with the preconditions it knows
about False -- neither seem like a good idea.
The details of the wording to accomplish that were very hard to arrive at,
and needed more polishing.
However, these cases are not at all important to the success or failure of
Ada 2012. Indeed, they are just short of a pathology; still, we need to have
an answer for them.
In particular, the case of inheriting a routine from a base type that is also
defined by an interface that is being added to a derived type is very unlikely
to occur in practice -- and if it does occur, the programmer has a major design
problem on their hands that will outweigh any rules we do or don't adopt.
Most of the time, when an interface is added to a derived type, the operations
to implement that interface are added at the same time. They're not inherited
from the parent type. Indeed, it doesn't make sense for them to be defined
on the parent type, because it doesn't make sense to have the operations
separately from the interface. For instance, if an interface implements
persistence, it makes little sense to add that to a type that already supports
persistence (it makes much more sense to add the interface to the base type
in that case).
Moreover, when the operations do already exist, it is unlikely that they
have the appropriate profile and contracts to implement the interface. So
again the interface will be implemented with new operations.
Of course, the programmer could be unlucky and have a collision of unrelated
routines. In that case, something will have to be renamed, and we surely will
not want the inherited routine to come through unchanged (it will be wrong for
one use or the other).
It is very unlikely that none of these things will happen, and everything is
perfect -- and that is the only case where the rules that we adopt for collisions
of preconditions and postconditions matter.
!corrigendum 3.9.2(20.2/2)
Replace the paragraph:
Force a conflict; the real text is found in the conflict file.
by:
Nothing interesting.
!corrigendum 6.1.1(0)
Insert new clause:
Force a conflict; the real text is found in the conflict file.
!ACATS test
An ACATS B-Test to check the newly revised legality rules should be
constructed.
ACATS C-Tests should be constructed to ensure that preconditions and
postconditions are evaluated as specified in this wording (and AI05-0247-1).
Specifically:
* Check that the specific precondition and the specific
postcondition of an inherited routine are evaluated (even though an
overridden routine would not have those specific pre and post conditions).
* Check that the class-wide preconditions of any (new) progenitors are
evaluated for a statically-bound call to an inherited routine.
* Check that the class-wide postconditions of any (new) progenitors are
evaluated for a statically-bound, dispatching, and access-to-subprogram
call to an inherited routine (of a type that doesn't have those
progenitors).
* Check that the class-wide preconditions of a dispatching call are those
of the nominal call. In particular, check the case where the dispatching
call is for type A, and the class-wide precondition is PA; the actual object
is type B derived from A with progenitor I, and class-wide precondition of
IA is PB, PB is True for the actual object and PA is False. In this case,
the precondition should fail on the dispatching call (while an incorrect
implementation using the precondition of the invoked call would succeed).
!ASIS
Any ASIS impact should be have been handled as part of the original
precondition/postcondition AI (AI05-0145-2) - this AI only affects dynamic
semantics and legality rules and neither of those are concerns of ASIS.
!appendix
From: Tucker Taft
Sent: Sunday, June 5, 2011 8:59 AM
I realize the hope was that all AIs would be settled by the June ARG meeting,
but I believe that AI-247 needs some face-to-face time. Because it is about
something which we see as the cornerstone of Ada 2012 (contracts), I don't think
we want to allow it to go into the standard without an informed consensus that
it is the "right" answer.
****************************************************************
From: Randy Brukardt
Sent: Monday, June 6, 2011 2:02 PM
> I realize the hope was that all AIs would be settled by the June ARG
> meeting, but I believe that AI-247 needs some face-to-face time.
> Because it is about something which we see as the cornerstone of Ada
> 2012 (contracts), I don't think we want to allow it to go into the
> standard without an informed consensus that it is the "right" answer.
Well, I don't agree with the notion that we would somehow change the major
thrust of the AI, given that there really isn't any other solutions that would:
(A) Preserve LSP in all language-defined situations; and
(B) Allow subprograms to "trust" their preconditions and allow callers to
"trust" their postconditions. ("trust" is in quotes because contract
expressions that have or depend on side-effects can never be trusted, but
the majority of contracts aren't in this category).
(A) is necessary for a variety of purposes, including allowing callers to know
the preconditions of called dispatching routines (necessary both for
understanding and for proof purposes). (B) is necessary so that these truly act
like contracts (and not random assertions), and programmers (and tools) can
reason about them.
I've always presumed that everyone agrees with (A) and (B) -- if not, we have
truly serious problems with the entire contract concept.
If so, the broad solution outlined in AI05-0247-1 is the only option. (Well,
there is one other option -- abandon all attempt to support contracts on OOP --
not acceptable IMHO).
We were bogged down by wording issues mostly having to do with specific unlikely
corner cases. These warrant some discussion, but it does not rise to the level
of reopening the AI.
If we *really* want a "informed consensus" on this issues, someone will need to
prepare a tutorial on these issues, as I suspect that many of the ARG members
are lost (especially when it comes to the details). That's going to take most of
the meeting by itself...
****************************************************************
From: Tucker Taft
Sent: Saturday, June 25, 2011 3:43 AM
6.1.1 paras 10-12:
If a type T inherits homographs from a parent type T1 and a progenitor
type T2, and
* the corresponding primitive subprogram P1 of type T1 is neither null nor abstract; and
* a class-wide precondition applies to the corresponding primitive subprogram P2 of
T2 that does not fully conform to any class-wide precondition that applies to P1,
then:
* If the type is abstract the implicitly declared subprogram P is *abstract*.
* Otherwise, the subprogram P *requires overriding* and shall be overridden
with a nonabstract subprogram.
****************************************************************
From: Tucker Taft
Sent: Saturday, June 25, 2011 4:00 AM
6.1.1 paras 10-12 again:
If a type T inherits homographs from a parent type T1 and a progenitor
type T2, and
* the corresponding primitive subprogram P1 of type T1 is neither null nor abstract; and
* the class-wide precondition True does not apply to P1; and
* a class-wide precondition applies to the corresponding primitive subprogram P2 of
T2 that does not fully conform to any class-wide precondition that applies to P1,
then:
* If the type T is abstract the implicitly declared subprogram P is *abstract*.
* Otherwise, the subprogram P *requires overriding* and shall be overridden
with a nonabstract subprogram.
****************************************************************
From: Tucker Taft
Sent: Saturday, June 25, 2011 4:04 AM
6.1.1 paras 10-12 (one more time):
If a type T has an implicitly declared subprogram P inherited
from a parent type T1 and a homograph of P from a
progenitor type T2, and
* the corresponding primitive subprogram P1 of type T1 is neither null nor
abstract; and
* the class-wide precondition True does not apply to P1; and
* a class-wide precondition applies to the corresponding primitive
subprogram P2 of T2 that does not fully conform to any class-wide
precondition that applies to P1,
then:
* If the type T is abstract the implicitly declared subprogram P is
*abstract*.
* Otherwise, the subprogram P *requires overriding* and shall be overridden
with a nonabstract subprogram.
****************************************************************
From: Tucker Taft
Sent: Saturday, June 25, 2011 4:08 AM
6.1.1 (26) and (27):
For any subprogram or entry call (including dispatching calls), the
specific precondition and postcondition checks that are performed are
determined by those of the subprogram or entry actually invoked. The
class-wide postcondition check that is performed on a call of a
primitive of type T includes class-wide postconditions inherited from
progenitors of T [Redundant: , even if the primitive is inherited from
a type T1 and these postconditions do not apply to the corresponding
primitive of T1].
We now take you to 3.9.2(20.4):
otherwise, the action is the same as the action for the corresponding
operation of the parent type or progenitor type from which the operation was
inherited {(except that additional class-wide postconditions may apply -- see
6.1.1)}. If there is more than one such corresponding operation, the action
is that for the operation that is not a null procedure, if any; otherwise,
the action is that of an arbitrary one of the operations.
Back to 6.1.6(27):
For a call via an access-to-subprogram value, the class-wide precondition
check performed is determined by the subprogram or entry denoted by the
prefix of the Access attribute reference that produced the value. In
contrast, the class-wide precondition check for other calls to a subprogram
or entry consists solely of checking the class-wide preconditions that apply
to the denoted entity (not necessarily the one that is invoked).
****************************************************************
From: Tucker Taft
Sent: Saturday, June 25, 2011 4:27 AM
6.1.1(27) again (now 2 paragraphs):
The class-wide precondition check for a call to a subprogram
or entry consists solely of checking the class-wide preconditions that apply
to the denoted callable entity (not necessarily the one that is invoked).
For a call via an access-to-subprogram value, all precondition and
postcondition checks performed are determined by the subprogram or entry
denoted by the prefix of the Access attribute reference that produced the
value.
****************************************************************
From: Randy Brukardt
Sent: Friday, July 22, 2011 10:41 PM
I had a note to index 6.1.1(24) as "unspecified". But I wonder if the wording is
completely right. This paragraph says:
If the assertion policy in effect at the point of a subprogram or entry
declaration is Check, then upon successful return from a call of the subprogram
or entry, prior to copying back any by-copy in out or out parameters, the
postcondition check is performed. This consists of the evaluation of the
specific and class-wide postcondition expressions that apply to the subprogram
or entry. If any of the postcondition expressions evaluate to False, then
Ada.Assertions.Assertion_Error is raised. The order of performing the checks is
not specified, and if any postcondition expression evaluates to False, it is not
specified whether any other postcondition expressions are evaluated. It is not
specified whether any constraint checks associated with copying back in out or
out parameters are performed before or after the postcondition checks.
"The order of performing the checks is not specified" is not the usual way of
saying this. Usually we would say "The checks are performed in an arbitrary
order". That's important as "arbitrary order" has a definition (1.1.4(18)) and
is used in other rules.
However, I have to wonder if there was any intent to avoid 1.1.4(18) with this
wording. I would guess not (Tucker is not usually that clever without
documenting that cleverness; and I know that my earlier rewrite of this
paragraph did not change this aspect). In particular, 6.4.1(6.17/3) does not
apply to pre/postcondition checks whether or not "arbitrary order" is used (the
precondition and postcondition expressions are not "direct constituents" of the
subprogram call that caused them to be checked).
So I would suggest that the last two sentences here be replaced by:
The checks are performed in an arbitrary order, and if any postcondition
expression evaluates to False, it is not specified whether any other
postcondition expressions are evaluated. The postcondition checks and constraint
checks associated with copying back in out or out parameters are performed in an
arbitrary order.
Any objections???
P.S. An unrelated aside: as 6.4.1(6.17/3) might apply to every place in the
standard that talks about "arbitrary order", I indexed all such places as
"arbitrary order, allowed", and added an AARM note after 6.4.1(6.17/3) to
suggest using the index to find those places, while noting that not all will
apply (for instance, ones solely associated with checks or finalization won't
trigger this rule).
P.P.S. I suspect a similar rewording will be needed somewhere in the
type_invariant section 7.3.2(0) -- I'll worry about that when I get to it in the
next few days.
****************************************************************
From: Jean-Pierre Rosen
Sent: Saturday, July 23, 2011 12:38 AM
Side question:
> If the assertion policy in effect at the point of a subprogram or
> entry
...
"subprogram or entry" is used several times in this paragraph. Why not use
"callable entity", which is defined (6(2)) to mean exactly that?
****************************************************************
From: Randy Brukardt
Sent: Saturday, July 23, 2011 2:07 AM
> Side question:
> > If the assertion policy in effect at the point of a subprogram or
> > entry
> ...
>
> "subprogram or entry" is used several times in this paragraph. Why not
> use "callable entity", which is defined
> (6(2)) to mean exactly that?
I didn't write this, but I suspect that it's because this wording talks about
calls quite a bit, and "call of the callable entity" is not particularly
readable.
And I don't find "callable entity" much more readable than "subprogram or entry"
(assuming there is no problem with other conjunctions, which doesn't seem to
happen in this paragraph). In my own writing, I'd almost always use "subprogram
or entry" (or just forget entry altogether, can't do that in the standard of
course). A savings of 4 characters per use doesn't make up for using "bigger"
words and an unfamiliar term.
I did a quick and sloppy check of the AARM, and found 43 occurrences of
"callable entity" (several in index entries) and 29 occurrences of "subprogram
or entry". I didn't make any effort to see which is used more in the normative
text, but it seems that each is acceptable.
I don't feel strongly, of course, so if there is a groundswell, I'll change it
(and should do so in all 29 places, I think).
****************************************************************
From: Tucker Taft
Sent: Saturday, July 23, 2011 8:16 AM
No objection. Or as Bob might say, "shrug."
> I had a note to index 6.1.1(24) as "unspecified". But I wonder if the
> wording is completely right. This paragraph says:
> ...
> "The order of performing the checks is not specified" is not the usual
> way of saying this. Usually we would say "The checks are performed in
> an arbitrary order". That's important as "arbitrary order" has a
> definition
> (1.1.4(18)) and is used in other rules.
> ...
> Any objections???
****************************************************************
From: Bob Duff
Sent: Saturday, July 23, 2011 8:51 AM
No objection from me, either. And no shrug, really
-- although this is hardly important, Randy's proposed change is exactly right.
As to "callable entity": shrug. But Randy's worry about "call of the callable
entity" is unwarranted -- just say "call of the entity"
or "call of that entity", and it's clear you're talking about the callable one
just mentioned.
Or just leave it alone.
****************************************************************
From: Tucker Taft
Sent: Saturday, July 23, 2011 9:03 AM
> No objection from me, either. And no shrug, really
> -- although this is hardly important, Randy's proposed change is
> exactly right.
Oh dear. Is this something like "putting words in your mouth"?
Perhaps "putting a shrug in your shoulders"? ;-)
I agree, Randy's proposed change is the right one.
****************************************************************
From: Randy Brukardt
Sent: Sunday, July 24, 2011 7:40 PM
> As to "callable entity": shrug. But Randy's worry about "call of the
> callable entity" is unwarranted -- just say "call of the entity"
> or "call of that entity", and it's clear you're talking about the
> callable one just mentioned.
>
> Or just leave it alone.
I'm definitely in "leave it alone" mode. I don't think your suggestion works
(everywhere). There is at least one paragraph that starts "For any subprogram
or entry call (including dispatching calls), ...". Changing this to "For any
callable entity call (including dispatching calls), ..." looks silly, and
replacing it by "For any call to an entity (including dispatching calls)..."
seems to me at least to have left out too much information (it leaves one
wondering what sort of calls are involved).
It's so late in the game that messing with correct wording doesn't seem
worthwhile, especially when there is plenty of wording that is not quite
right...
****************************************************************
From: Randy Brukardt
Sent: Saturday, July 23, 2011 12:40 AM
I'm worried about the fact that class-wide preconditions have a default value of
True if not specified.
That's absolutely necessary for the conformance rules of 6.1.1(10-13/3) to work.
For instance, the revised wording from the recent meeting includes the following
bullet:
* there is a class-wide precondition that applies to the corresponding primitive
subprogram P2 of T2 that does not fully conform to any class-wide precondition
that applies to P1,
We need the class-wide precondition to be True by default to catch cases like
the following:
package P1 is
type T1 is tagged private;
function Is_OK (Obj : T1) return Boolean;
procedure P (Obj : in out T1)
with Precondition'Class => Is_OK (Obj);
end P1;
package P2 is
type T2 is interface;
procedure P (Obj : in out T2); -- No class-wide precondition specified here.
end P2;
with P1, P2;
package P3 is
type T is new P1.T1 and P2.T2 with ...;
-- Inherits P1.P here.
end P3;
This violates the rule above as True (the class-wide precondition of P2.P) does
not conform to any class-wide precondition of P1.P (the only one is Is_OK
(Obj)). If instead P2.P has no class-wide precondition, we'd need a more complex
rule to detect this case (and we'd also need a more complex second bullet to
except any subprogram that has any class-wide precondition of True).
Perhaps an even more important reason that we need True to be the default is so
that every tagged primitive has at least one class-wide precondition. If a
routine like P2.P had no class-wide precondition, it would be possible to add
one later for a descendant, which by definition would be stronger and would
violate LSP.
Also note that class-wide preconditions are not inherited in any way; checking a
preconditions involves checking the class-wide preconditions of yourself and of
all ancestors. So each subprogram has a single, unique class-wide precondition.
However, this does not work in the common case that all we want for an extension
is the same precondition as it's parent:
with P1;
package P3 is
type T3 is new P1.T1 with private;
overriding
procedure P (Obj : in out T2);
end P2;
Here the language says that the class-wide precondition of P3.P is True. This is
almost certainly not what we want (we probably just want to use the parent's
class-wide precondition). True would of course eliminate the precondition of the
parent from consideration. What we really want is for this routine to have no
class-wide precondition of it's own.
Note that none of the other contracts have this problem (extra True class-wide
postconditions have no effect, and specific ones aren't inherited anyway).
One solution would be to not have a default value from class-wide preconditions,
but then the wording of 6.1.1(10-13/3) will have to be totally redone, and other
wording would also have to be changed to avoid problems with later adding of
preconditions to descendants.
A better (maybe?) solution would be for the value of True to be the default if
and only if no other class-wide precondition "applies" to the subprogram; if any
other class-wide precondition applies, then there is no class-wide precondition
directly for this entity (of course, the class-wide preconditions that come from
ancestors still apply).
This would require wording like (6.1.1(3/3)):
This aspect specifies a class-wide precondition for a callable entity and its
descendants; it shall be specified by an expression, called a class-wide
precondition expression. If not specified for an entity, then if no other
class-wide precondition applies to the entity, the class-wide precondition
expression for the entity is the enumeration literal True.
AARM Ramification:
If other class-wide preconditions apply to the entity and no class-wide
precondition is specified, no class-wide precondition is defined for the entity;
of course, the class-wide preconditions (of ancestors) that apply are still
going to be checked. We need routines that don't have ancestors and don't
specify a class-wide precondition to have a class-wide precondition of True, so
that adding such a precondition to a descendant has no effect (necessary as a
dispatching call through the root routine would not check any class-wide
precondition).
[I think I prefer to not say anything normative about the unspecified/"applies"
case; the entire explanation of the Ramification is necessary to avoid
confusion, and that is just too much verbiage.]
Can this be done better??
****************************************************************
From: Tucker Taft
Sent: Saturday, July 23, 2011 8:22 AM
Your proposed wording seems reasonable to me.
****************************************************************
From: Bob Duff
Sent: Saturday, July 23, 2011 4:29 PM
> This aspect specifies a class-wide precondition for a callable entity
> and its descendants; it shall be specified by an expression, called a
> class-wide precondition expression. If not specified for an entity,
> then if no other class-wide precondition applies to the entity, the
> class-wide precondition expression for the entity is the enumeration literal True.
I suggest leaving out "enumeration literal". It's not really a piece of syntax
that gets resolved (e.g. if there's some other True lying around we don't use
that). It's the True of boolean that live in Standard, and just saying "True"
seems clear enough.
Otherwise, I agree with the above wording.
> AARM Ramification:
> If other class-wide preconditions apply to the entity and no
> class-wide precondition is specified, no class-wide precondition is
> defined for the entity; of course, the class-wide preconditions (of
> ancestors) that apply are still going to be checked. We need routines
> that
"routines" --> "subprograms"?
> don't have ancestors and don't specify a class-wide precondition to
> have a class-wide precondition of True, so that adding such a
> precondition to a descendant has no effect (necessary as a dispatching
> call through the root routine would not check any class-wide precondition).
****************************************************************
From: Randy Brukardt
Sent: Sunday, July 24, 2011 7:33 PM
> I suggest leaving out "enumeration literal". It's not really a piece
> of syntax that gets resolved (e.g. if there's some other True lying
> around we don't use that). It's the True of boolean that live in
> Standard, and just saying "True" seems clear enough.
I don't agree, as this is subject to full conformance in following Legality
Rules and being clear that we're not talking about the value True but rather a
specific expression True is important. (Full conformance depends on the specific
declaration involved.) Indeed, I worry that this is still too sloppy and we
really ought to say "enumeration literal True declared in package Standard (see
A.1)". Probably a To Be Honest would be good enough for that.
****************************************************************
From: Randy Brukardt
Sent: Saturday, July 23, 2011 1:53 AM
The wording approved at the meeting for 6.1.1(26/3) says:
For any subprogram or entry call (including dispatching calls), the specific
precondition and postcondition checks that are performed are determined by those
of the subprogram or entry actually invoked. The class-wide postcondition check
that is performed on a call of a primitive of type T includes class-wide
postconditions inherited from progenitors of T [Redundant: , even if the
primitive is inherited from a type T1 and these postconditions do not apply to
the corresponding primitive of T1].
The major problem here is that there is no such thing as a "specific
postcondition check" or a "class-wide postcondition check". There are specific
and class-wide postcondition *expressions*, but there is just a single
postcondition check that covers everything (see 6.1.1(24)).
This was the definition that Tucker had originally given the check, and (unlike
the precondition cases), there was no semantic reason to split it up (they all
work the same way, raise an exception if False). So it remains a single check.
The second sentence here seems to be structured to recognize this reality (it
seems to indicate that there is only a single class-wide postcondition check,
which is bizarre as there never was any such model).
We could fix this by defining these as separate checks, but that would explode
6.1.1(24) from one paragraph into four (if the precondition wording is any
guide.
Alternatively, we could return the wording somewhat closer to its original form
(but note this is the wording that Erhard found confusing - I added a
parenthetical remark to clarify):
For any subprogram or entry call (including dispatching calls), the specific
precondition check and the postcondition check [Redundant: (which checks both
specific and class-wide postcondition expressions, if any)] that are performed
are determined by those of the subprogram or entry actually invoked. The
postcondition check that is performed on a call of a primitive of type T
includes class-wide postcondition expressions inherited from progenitors of T
[Redundant: , even if the primitive is inherited from a type T1 and these
postconditions do not apply to the corresponding primitive of T1].
A second (minor) problem is that class-wide postcondition expressions are not
"inherited"; they "apply", so the phrase "includes class-wide postconditions
inherited from progenitors of T" is nonsense in normative text. (And "class-wide
postconditions" is a term that is not defined at all). We could say something
like "includes class-wide postconditions expressions that apply solely because
they are defined for primitives of progenitors of T", but that seems pretty
wordy. Still, I don't have a better idea. That gives:
For any subprogram or entry call (including dispatching calls), the specific
precondition check and the postcondition check [Redundant: (which checks both
specific and class-wide postcondition expressions, if any)] that are performed
are determined by those of the subprogram or entry actually invoked. The
postcondition check that is performed on a call of a primitive of type T
includes class-wide postcondition expressions that apply solely because they
are defined for primitives of progenitors of T [Redundant: , even if the
primitive is inherited from a type T1 and these postconditions do not apply to
the corresponding primitive of T1].
Any improvements will be appreciated.
****************************************************************
From: Tucker Taft
Sent: Saturday, July 23, 2011 8:22 AM
> Alternatively, we could return the wording somewhat closer to its
> original form (but note this is the wording that Erhard found
> confusing - I added a parenthetical remark to clarify):
>
> For any subprogram or entry call (including dispatching calls), the specific
> precondition check and the postcondition check [Redundant: (which checks both specific
> and class-wide postcondition expressions, if any)] that are performed are determined by
> those of the subprogram or entry actually invoked. The postcondition
> check that is performed on a call of a primitive of type T includes class-wide
> postcondition expressions inherited from progenitors of T [Redundant: , even if the
> primitive is inherited from a type T1 and these postconditions do not apply to the
> corresponding primitive of T1].
I would prefer:
For any subprogram or entry call (including dispatching calls), the checks
that are performed for specific precondition expressions and all
postcondition expressions are determined by those of the subprogram
or entry actually invoked. The postcondition check that is performed
on a call of a primitive of type T verifies class-wide postcondition
expressions inherited from progenitors of T ...
This finesses the whole issue of whether it is considered one check or two,
since the plural "checks" is correct either way. It also emphasizes "all"
instead of simply allowing it to be implicit in the use of the unqualified term
"postcondition check." It also avoids the notion that an expression is
"included" in a check, since it is really the evaluation of the expression and
the check that it is true that is included in the check, and the term "verify"
means exactly that.
> A second (minor) problem is that class-wide postcondition expressions
> are not "inherited"; they "apply", so the phrase "includes class-wide
> postconditions inherited from progenitors of T" is nonsense in
> normative text. (And "class-wide postconditions" is a term that is not
> defined at all). We could say something like "includes class-wide
> postconditions expressions that apply solely because they are defined
> for primitives of progenitors of T", but that seems pretty wordy.
> Still, I don't have a better idea. That gives:
>
> For any subprogram or entry call (including dispatching calls), the specific
> precondition check and the postcondition check [Redundant: (which checks both specific
> and class-wide postcondition expressions, if any)] that are performed are determined by
> those of the subprogram or entry actually invoked. The postcondition
> check that is performed on a call of a primitive of type T includes class-wide
> postcondition expressions that apply solely because they are defined for primitives of
> progenitors of T [Redundant: , even if the primitive is inherited from a type T1 and
> these postconditions do not apply to the corresponding primitive of T1].
>
> Any improvements will be appreciated.
Not real happy with that, especially the "apply solely because" part.
How about:
For any subprogram or entry call (including dispatching calls), the checks
that are performed that verify specific precondition expressions and
specific and class-wide postcondition expressions are determined by those
for the subprogram or entry actually invoked. Note that the class-wide
postcondition expressions verified by the postcondition check that is part
of a call on a primitive of type T includes all class-wide postcondition
expressions originating in any progenitor of T [Redundant: , even if the
primitive called is inherited from a type T1 and some of the postcondition
expressions do not apply to the corresponding primitive of T1].
****************************************************************
From: Randy Brukardt
Sent: Sunday, July 24, 2011 11:36 PM
I wasn't happy about having to fix it in order to finish that AI, especially at
2 am. And it surely wasn't my best work (whose is at the end of a long day?).
Anyway, I think this is fine and addresses the problems with the previous
version. I'll use it pending any further suggestions.
****************************************************************
From: Erhard Ploedereder
Sent: Monday, July 25, 2011 5:55 AM
> The checks are performed in an arbitrary order, and if any
> postcondition expression evaluates to False, it is not specified
> whether any other postcondition expressions are evaluated. The
> postcondition checks and constraint checks associated with copying
> back in out or out parameters are performed in an arbitrary order.
I found it "syntactically" strange that we should state omission of subsequent
checks only for postconditions and not for constraint checks. The previous
wording applied to both checks. To fix this, here is a suggested rewrite:
-------------
The evaluation of the postcondition expressions and any constraint checks
associated with copying back in out or out parameters are performed in an
arbitrary order. If any postcondition expression evaluates to False or any
constraint check fails, it is not specified whether any other checks are
evaluated.
--------------
Note that I replaced "postcondition check" by "evaluation of the p.c.e."
The reason is that the manual defined "the postcondition check" as the
combination of these evaluations (see the first two sentences of 6.1.1. (24/3).
Incidently, 22/3 and 23/3 have the same problem for the class-wide precondition
check. It ought to be fixed there, too.
Sideremark: Maybe the term "xx-condition check" should be used for the
evaluation of any inidividual xx-condition expression that applies, rather than
to any combination of them. It would make the wording simpler.
****************************************************************
From: Bob Duff
Sent: Monday, July 25, 2011 8:20 AM
> I'm definitely in "leave it alone" mode.
Good.
>... I don't think your suggestion works (everywhere).
My suggestion was for this paragraph alone.
I forgot to reply to this part of your earlier message:
> I don't feel strongly, of course, so if there is a groundswell, I'll
> change it (and should do so in all 29 places, I think).
I am strongly opposed to changing 29 places where wording already exists from
Ada 2005 and before! JPR suggested a minor change to this particular paragraph,
which IMHO should have no bearing on 29 others. I'm all for consistency, but
you pointed out that we use both "callable entity" and "subp or entry" all over,
so lets live with that.
****************************************************************
From: Jean-Pierre Rosen
Sent: Monday, July 25, 2011 9:07 AM
> I am strongly opposed to changing 29 places where wording already
> exists from Ada 2005 and before! JPR suggested a minor change to this
> particular paragraph, which IMHO should have no bearing on 29 others.
> I'm all for consistency, but you pointed out that we use both
> "callable entity" and "subp or entry" all over, so lets live with
> that.
Agreed.
What happened is that I looked at that paragraph, asked myself "Oh, he didn't
use 'callable entity', is there some subtle difference between 'callable entity'
and 'subp or entry' ?", checked and saw there was none, and therefore suggested
to use the proper term.
If there are 29 of these, forget it.
(apparently the term "callable entity" did not exist in Ada83, it was introduced
with Ada 95, with probably no backward patch)
****************************************************************
From: Bob Duff
Sent: Monday, July 25, 2011 9:52 AM
> (apparently the term "callable entity" did not exist in Ada83, it was
> introduced with Ada 95, with probably no backward patch)
Right, it didn't exist in Ada 83.
I invented the term "callable entity", because I thought it would simplify
things, and as far as I remember, I intended to switch over to this term
everywhere. I don't know why there are still occurrences of "subprogram or
entry": I missed some? Or some got added since Ada 9X? Or during Ada 9X, by
people I failed to inform about it? Or the wording works better with "subprogram
or entry" in some places?
Anyway, I don't think we should worry about "fixing" this little inconsistency.
Note that I've gotting closer to the "if it ain't broke, don't fix it"
philosophy, for the RM, since Ada 9X days. In those days, I was itching to fix
anything that looked like a bug.
****************************************************************
From: Erhard Ploedereder
Sent: Monday, July 25, 2011 6:04 AM
> A better (maybe?) solution would be for the value of True to be the
> default if and only if no other class-wide precondition "applies" to
> the subprogram; if any other class-wide precondition applies, then
> there is no class-wide precondition directly for this entity (of
> course, the class-wide preconditions that come from ancestors still
> apply).
Indeed.
And as far as the rest of Randy's message goes, that merely enforces my earlier
sideremark about the meaning of "check". We ought to define the xx-checks to
mean the evaluation of any new or "applying" xx expression plus the raising of
the exception as needed. All other texts could then talk in term of failing of
sucessful checks as for all the other checking that goes on.
****************************************************************
From: Tucker Taft
Sent: Monday, July 25, 2011 6:30 AM
"applying" ==> "applicable".
"sucessful" ==> "successful"
****************************************************************
From: Randy Brukardt
Sent: Monday, July 25, 2011 9:47 PM
> And as far as the rest of Randy's message goes, that merely enforces
> my earlier sideremark about the meaning of "check".
> We ought to define the xx-checks to mean the evaluation of any new or
> "applying" xx expression plus the raising of the exception as needed.
> All other texts could then talk in term of failing of sucessful
> checks as for all the other checking that goes on.
"Check" in Ada *never* includes the raising of an exception. See the many, many
places in the Ada Standard where we say "xxx_Error is raised if this check
fails". (Some examples: 13.11.4(27/3), 4.6(57), 4.8(10.1/3), ...)
And as far as the rest of it goes, that's exactly what it says. At least, I
can't see any substantive difference. The only thing we care about (or should
care about, IMHO) is that the postcondition check can be made before or after
the parameters are copied back. There is no need for more flexibility than that.
In any case, what you're asking for is a near complete rewrite of the dynamic
semantics sections of 6.1.1 (and also of 7.3.2, type invariants, which uses the
same wording). Most likely, the rewrite will introduce a new batch of bugs.
(There have been plenty in the existing wording.) And you're asking for it to be
inconsistent with the rest of the Standard. So I'd much prefer to use editor's
discression and ignore this suggestion.
****************************************************************
From: Randy Brukardt
Sent: Monday, July 25, 2011 9:28 PM
> > The checks are performed in an arbitrary order, and if any
> > postcondition expression evaluates to False, it is not specified
> > whether any other postcondition expressions are evaluated. The
> > postcondition checks and constraint checks associated with copying
> > back in out or out parameters are performed in an arbitrary order.
>
> I found it "syntactically" strange that we should state omission of
> subsequent checks only for postconditions and not for constraint
> checks.
The wording you quoted is wrong anyway, there is only one postcondition check.
(I've fixed that in the wording used in the Standard; it's quite different than
what was in my e-mail.) In any case, what the permission does is allows omitting
part of the check once it has been determined it fails.
A compiler never has to make other checks once one fails -- if we need to say
that explicitly here, there would be a hundred other places in the standard
where we would need to say the same thing. And if none of the checks fails, they
all have to be made. (Constraint checks can't have side effects anyway, so it's
not clear to me that anyone can tell whether they are made when they succeed.)
For one example, the (normal) wording for copy back of parameters (in
6.4.1(17)) says:
After normal completion and leaving of a subprogram, for each in out or out
parameter that is passed by copy, the value of the formal parameter is converted
to the subtype of the variable given as the actual parameter and assigned to it.
These conversions and assignments occur in an arbitrary order.
There is nothing here about omitting checks if one fails. I don't see any reason
that we need to add such wording for postconditions or anywhere else for that
matter.
****************************************************************
From: Erhard Ploedereder
Sent: Tuesday, July 26, 2011 4:43 AM
> There is nothing here about omitting checks if one fails. I don't see
> any reason that we need to add such wording for postconditions or
> anywhere else for that matter.
Well, my point was that it was there before your rewrite. In the end, we want
short-circuit semantics for the pre-/postcondition checks, no specified order of
the term evaluations and (arguably?) no specified order with constraint checks
on entry and exit.
> there is only one postcondition check
Definitely NOT true in the version of the RM in Edinburgh, which knows of two
postcondition checks: 6.1.1. says
" ... precondition checks are performed as follows:
The specific precondition check begins with ...
The class-wide precondition check begins with ....
Now, I don't like that for all the reasons that you cite, but I would go in the
opposite direction and make each expression a check. Then the "no more checks
once the exception is raised" indeed takes care of short-circuit semantics
without the need for extra words.
****************************************************************
From: Randy Brukardt
Sent: Tuesday, July 26, 2011 5:12 PM
> > There is nothing here about omitting checks if one fails. I
> don't see
> > any reason that we need to add such wording for postconditions or
> > anywhere else for that matter.
>
> Well, my point was that it was there before your rewrite.
OK, and my point was that we don't need it, so it doesn't matter if it is there or not.
> In
> the end, we want short-circuit semantics for the pre-/postcondition
> checks, no specified order of the term evaluations and (arguably?) no
> specified order with constraint checks on entry and exit.
Right.
> > there is only one postcondition check
>
> Definitely NOT true in the version of the RM in Edinburgh, which knows
> of two postcondition checks:
> 6.1.1. says
> " ... precondition checks are performed as follows:
> . The specific precondition check begins with ...
> . The class-wide precondition check begins with ....
Umm, I said there was only one POSTcondition check, and you responded with two
rules about PREcondition checks. Those aren't related (yes, there are multiple
precondition checks, but we were not talking about that rule). [Aside: One of
the big mistakes that we made early on with pre- and postconditions was assuming
that the rules for them could be the same; it turns out that they are wildly
different and very little about them should be the same. That was the lesson of
AI05-0247-1.]
> Now, I don't like that for all the reasons that you cite, but I would
> go in the opposite direction and make each expression a check. Then
> the "no more checks once the exception is raised" indeed takes care of
> short-circuit semantics without the need for extra words.
I think you are so confused now that it would be better if you started over with
the entire clause and reviewed it as a whole (and then decide whether you think
there are any problems). On top of that, you don't even have the current
wording. So I suggest you review the entire clause when the review version of
the Standard comes out in a week or so and we can take this discussion up again
(if necessary) once you've done that.
(I need to get the rest of the Standard done so everyone can review it - the
deadline for starting that review is upon us and I still have a number of days
of work to finish.)
****************************************************************
Questions? Ask the ACAA Technical Agent