How to Write Object Oriented Contracts ====================================== Object Oriented Programming (OOP) may require the use of special :ref:`Class-Wide Subprogram Contracts` for dispatching subprograms, so that |GNATprove| can check Liskov Substitution Principle on every overriding subprogram. Object Oriented Code Without Dispatching ---------------------------------------- In the special case where OOP is used without dispatching, it is possible to use the regular :ref:`Subprogram Contracts` instead of the special :ref:`Class-Wide Subprogram Contracts`. For example, consider a variant of the ``Logging`` and ``Range_Logging`` units presented in :ref:`Class-Wide Subprogram Contracts`, where no dispatching is allowed. Then, it is possible to use regular preconditions and postconditions as contracts, provided ``Log_Type`` is publicly declared as an untagged private type in both units: .. literalinclude:: /examples/ug__logging_no_dispatch/logging_no_dispatch.ads :language: ada :linenos: .. literalinclude:: /examples/ug__range_logging_no_dispatch/range_logging_no_dispatch.ads :language: ada :linenos: .. index:: Pre'Class; how to use Post'Class; how to use Writing Contracts on Dispatching Subprograms -------------------------------------------- Whenever dispatching is used, the contract that applies in proof to a dispatching call is the class-wide contract, defined as the first one present in the following list: #. the class-wide precondition (resp. postcondition) attached to the subprogram #. or otherwise the class-wide precondition (resp. postcondition) being inherited by the subprogram from the subprogram it overrides #. or otherwise the default class-wide precondition (resp. postcondition) of ``True``. For abstract subprograms (on interfaces or regular tagged types), only a class-wide contract can be specified. For other dispatching subprograms, it is possible to specify both a regular contract and a class-wide contract. In such a case, |GNATprove| uses the regular contract to analyze static calls to the subprogram and the class-wide contract to analyze dispatching calls to the subprogram, and it checks that the specific contract is a refinement of the class-wide contract, as explained in :ref:`Mixing Class-Wide and Specific Subprogram Contracts`. Let's consider the various cases that may occur when overriding a subprogram: .. literalinclude:: /examples/ug__geometry/geometry.ads :language: ada :linenos: In package ``Geometry``, a type ``Shape`` is derived in a type ``Rectangle``. A function ``Shape.Valid`` defines what it is to be a valid shape. It is overridden by ``Rectangle.Valid`` which defines what it is to be a valid rectangle. Here, a valid rectangle is also a valid shape, but that need not be the case. Procedure ``Set_Default`` and its variants demonstrate the various configurations that can be found in practice: 1. The overridden subprogram ``Shape.Set_Default`` defines a class-wide contract (here only a postcondition), which is inherited in the overriding subprogram ``Rectangle.Set_Default``. By the semantics of Ada, the postcondition of ``Shape.Set_Default`` calls ``Shape.Valid``, while the inherited postcondition of ``Rectangle.Set_Default`` calls ``Rectangle.Valid``. 2. Both the overridden subprogram ``Shape.Set_Default_Repeat`` and the overriding subprogram ``Rectangle.Set_Default_Repeat`` define a class-wide contract (here only a postcondition). Here, since the contract is simply repeated, this is equivalent to case 1 above of inheriting the contract: the postcondition of ``Shape.Set_Default_Repeat`` calls ``Shape.Valid``, while the postcondition of ``Rectangle.Set_Default_Repeat`` calls ``Rectangle.Valid``. 3. Only the overriding subprogram ``Rectangle.Set_Default_No_Post`` defines a class-wide contract (here only a postcondition). The default class-wide postcondition of ``True`` is used for the overridden ``Shape.Set_Default_No_Post``. .. index:: Liskov Substitution Principle In case 1, the overriding subprogram satisfies Liskov Substitution Principle by construction, so |GNATprove| emits no check in that case. Note that this is not the same as saying that ``Shape.Set_Default`` and ``Rectangle.Set_Default`` have the same contract: here the two postconditions differ, as one calls ``Shape.Valid``, while the other calls ``Rectangle.Valid``. In case 2, |GNATprove| checks that Liskov Substitution Principle is verified between the contracts of the overridden and the overriding subprograms. Here, it checks that the postcondition of ``Rectangle.Set_Default_Repeat`` is stronger than the postcondition of ``Shape.Set_Default_Repeat``. In case 3, |GNATprove| also checks that Liskov Substitution Principle is verified between the default contract of the overridden subprogram and the specified contract of the overriding subprograms. Here, only a postcondition is specified for ``Rectangle.Set_Default_No_Post``, so it is indeed stronger than the default postcondition of ``Shape.Set_Default_No_Post``. Hence the results of |GNATprove|'s analysis on this program: .. literalinclude:: /examples/ug__geometry/test.out :language: none Let's consider now calls to these subprograms in procedure ``Use_Geometry``: .. literalinclude:: /examples/ug__use_geometry/use_geometry.adb :language: ada :linenos: Here are the results of |GNATprove|'s analysis on this program: .. literalinclude:: /examples/ug__use_geometry/test.out :language: none Parameter ``S`` is of class-wide type ``Shape'Class``, so it can be dynamically of both types ``Shape`` or ``Rectangle``. All calls on ``S`` are dispatching. In this program, |GNATprove| needs to check that the precondition of the calls to ``Operate`` is satisfied. As procedures ``Shape.Set_Default`` and ``Shape.Set_Default_Repeat`` state precisely this condition in postcondition, the precondition to the calls to ``Operate`` that follow can be proved. As procedure ``Shape.Set_Default_No_Post`` has no postcondition, the precondition to the last call to ``Operate`` cannot be proved. Note that these proofs take into account both possible types of ``S``, for example: * If ``S`` is dynamically a shape, then the call to ``Shape.Set_Default`` on line 7 ensures that ``Shape.Valid`` holds, which ensures that the precondition to the call to ``Shape.Operate`` is satisfied on line 8. * If ``S`` is dynamically a rectangle, then the call to ``Rectangle.Set_Default`` on line 7 ensures that ``Rectangle.Valid`` holds, which ensures that the precondition to the call to ``Rectangle.Operate`` is satisfied on line 8. Writing Contracts on Subprograms with Class-wide Parameters ----------------------------------------------------------- Subprograms with class-wide parameters are not in general dispatching subprograms, hence they are specified through regular :ref:`Subprogram Contracts`, not :ref:`Class-Wide Subprogram Contracts`. Inside the regular contract, calls on primitive subprograms of the class-wide parameters are dispatching though, like in the code. For example, consider procedure ``More_Use_Geometry`` which takes four class-wide parameters of type ``Shape'Class``, which can all be dynamically of both types ``Shape`` or ``Rectangle``: .. literalinclude:: /examples/ug__more_use_geometry/more_use_geometry.adb :language: ada :linenos: The precondition of ``More_Use_Geometry`` specifies that ``S1.Valid`` holds, which takes into account both possible types of ``S1``: * If ``S1`` is dynamically a shape, then the precondition specifies that ``Shape.Valid`` holds, which ensures that the precondition to the call to ``Shape.Operate`` is satisfied on line 8. * If ``S1`` is dynamically a rectangle, then the precondition specifies that ``Rectangle.Valid`` holds, which ensures that the precondition to the call to ``Rectangle.Operate`` is satisfied on line 8. Similarly, the test on ``S2.Valid`` on line 10 ensures that the precondition to the call to ``S2.Operate`` on line 11 is satisfied, and the call to ``S3.Set_Default`` on line 14 ensures through its postcondition that the precondition to the call to ``S3.Operate`` on line 15 is satisfied. But no precondition or test or call ensures that the precondition to the call to ``S4.Operate`` on line 17 is satisfied. Hence the results of |GNATprove|'s analysis on this program: .. literalinclude:: /examples/ug__more_use_geometry/test.out :language: none