Version 1.3 of ai05s/ai05-0254-1.txt
!standard 6.1.1(0) 11-07-22 AI05-0254-1/02
!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
**TBD.
!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.4(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 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).
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 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].
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 legality rules should be constructed. Possibly,
an ACATS C-Test should be constructed to ensure that postconditions are
evaluated as specified in this wording.
!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 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: 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: 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).
****************************************************************
Questions? Ask the ACAA Technical Agent