You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add contracts docs for order-of-evaluation and contract predicates
BOOTSTRAP NOTE FOR CONTRIBUTORS: After taking this commit, just remember to build cppfront from the Cpp1 sources first as usual (see https://hsutter.github.io/cppfront/#how-do-i-get-and-build-cppfront ). Then you'll be able to compile the updated reflect.h2 in this commit just fine.
Renamed the provided contract groups to lowercase descriptive names; e.g., Type -> type_safety
Closes #1018
Renamed .has_handler to .is_active, which makes more sense for contract groups that don't have replaceable handlers
Reordered violation test to check predicates first, before grp.is_active
Copy file name to clipboardExpand all lines: docs/cpp2/contracts.md
+65-20Lines changed: 65 additions & 20 deletions
Original file line number
Diff line number
Diff line change
@@ -15,13 +15,23 @@ Notes:
15
15
16
16
- Optionally, `condition` may be followed by `, "message"`, a message to include if a violation occurs. For example, `pre(condition, "message")`.
17
17
18
-
- Optionally, a `<Group>` can be written inside `<``>` angle brackets immediately before the `(`, to designate that this test is part of the [contract group](#contract-groups) named `Group`. If a violation occurs, `Group.report_violation()` will be called. For example, `pre<Group>(condition)`.
18
+
- Optionally, a `<group, pred1, pred2>` can be written inside `<``>` angle brackets immediately before the `(`, to designate that this test is part of the [contract group](#groups) named `group` and (also optionally) [contract predicates](#predicates)`pred1` and `pred2. If a violation occurs, `Group.report_violation()` will be called. For example, `pre<group>(condition)`.
19
+
20
+
The order of evaluation is:
21
+
22
+
- First, predicates are evaluated in order. If any predicte evaluates to `#!cpp false`, stop.
23
+
24
+
- Next, `group.is_active()` is evaluated. If that evaluates to `#!cpp false`, stop.
25
+
26
+
- Next, `condition` is evaluated. If that evaluates to `#!cpp true`, stop.
27
+
28
+
- Finally, if all the predicates were true and the group is active and the condition was false, `group.report_violation()` is called.
19
29
20
30
For example:
21
31
22
32
```cpp title="Precondition and postcondition examples" hl_lines="2 3"
23
33
insert_at: (container, where: int, val: int)
24
-
pre<Bounds>( 0 <= where <= vec.ssize(), "position (where)$ is outside 'val'" )
34
+
pre<bounds>( 0 <= where <= vec.ssize(), "position (where)$ is outside 'val'" )
25
35
post ( container.ssize() == container.ssize()$ + 1 )
26
36
= {
27
37
_ = container.insert( container.begin()+where, val );
@@ -37,27 +47,31 @@ In this example:
37
47
- The postcondition is part of the `Default` safety contract group. If the check fails, then `#!cpp cpp2::Default.report_violation()` is called.
38
48
39
49
40
-
## <a id="contract-groups"></a> Contract groups
50
+
## <a id="groups"></a> Contract groups
51
+
52
+
Contract groups are useful to enable or disable or [set custom handlers](#violation-handlers) independently for different groups of contracts. A contract group `grp` is just the name of an object that can be called with:
41
53
42
-
Contract groups are useful to enable or disable or [set custom handlers](#violation-handlers) independently for different groups of contracts. A contract group `G` is just the name of an object that can be called with `G.report_violation()` and `G.report_violation(message)`, where `message` is a `* const char` C-style text string.
54
+
- `grp.report_violation()` and `grp.report_violation(message)`, where `message` is a `* const char` C-style text string
55
+
56
+
- `grp.is_active()`, which returns `#!cpp true` if and only if the group is enabled
43
57
44
58
You can create new contract groups just by creating new objects that have a `.report_violation` function. The object's name is the contract group's name. The object can be at any scope: local, global, or heap.
45
59
46
60
For example, here are some ways to use contract groups, for convenience using [`cpp2::contract_group`](#violation_handlers) which is a convenient group type:
GroupA: cpp2::contract_group = (); // a global group
63
+
group_a: cpp2::contract_group = (); // a global group
50
64
51
65
func: () = {
52
-
GroupB: cpp2::contract_group = (); // a local group
66
+
group_b: cpp2::contract_group = (); // a local group
53
67
54
-
GroupC := new<cpp2::contract_group>(); // a dynamically allocated group
68
+
group_c := new<cpp2::contract_group>(); // a dynamically allocated group
55
69
56
70
// ...
57
71
58
-
assert<GroupA >( some && condition );
59
-
assert<GroupB >( another && condition );
60
-
assert<GroupC*>( another && condition );
72
+
assert<group_a >( some && condition );
73
+
assert<group_g >( another || condition );
74
+
assert<group_c*>( another && condition );
61
75
}
62
76
```
63
77
@@ -81,6 +95,36 @@ function_declaration: @copyable type =
81
95
```
82
96
83
97
98
+
## <aid="predicates"></a> Contract predicates
99
+
100
+
Contract predicates are useful to conditionally check specific contracts as a static or dynamic property. Importantly, if any predicate is `#!cpp false`, the check's conditional expression will not be evaluated.
-`is_checked_build` is evaluated. Since it is a compile-time value, the evaluation can happen at compile time. If it evaluates to `#!cpp false`, then stop; the entire contract could be optimized away by the compiler.
118
+
119
+
- Otherwise, next `checking_enabled` is evaluated at run time. If it evaluates to `#!cpp false`, then stop.
120
+
121
+
- Otherwise, next `audit.is_active()` is evaluated. If it evaluates to `#!cpp false`, then stop.
122
+
123
+
- Otherwise, next `condition` is evaluated. If it evaluates to `#!cpp true`, then stop.
124
+
125
+
- Otherwise, `audit.report_violation()` is called.
126
+
127
+
84
128
## <aid="violation-handlers"></a> `cpp2::contract_group`, and customizable violation handling
85
129
86
130
The contract group object could also provide additional functionality. For example, Cpp2 comes with the `cpp2::contract_group` type which allows installing a customizable handler for each object. Each object can only have one handler at a time, but the handler can change during the course of the program. `contract_group` supports:
@@ -89,34 +133,35 @@ The contract group object could also provide additional functionality. For examp
89
133
90
134
-`.get_handler()` returns the current handler function pointer, or null if none is installed.
91
135
92
-
-`.has_handler()` returns whether there is a current handler installed.
136
+
-`.is_active()` returns whether there is a current handler installed.
93
137
94
138
-`.enforce(condition, message)` evaluates `condition`, and if it is `false` then calls `.report_violation(message)`.
95
139
96
140
Cpp2 comes with five predefined `contract group` global objects in namespace `cpp2`:
97
141
98
-
-`Default`, which is used as the default contract group for contracts that don't specify a group.
142
+
-`default`, which is used as the default contract group for contracts that don't specify a group.
99
143
100
-
-`Type` for type safety checks.
144
+
-`type_safety` for type safety checks.
101
145
102
-
-`Bounds` for bounds safety checks.
146
+
-`bounds_safety` for bounds safety checks.
103
147
104
-
-`Null` for null safety checks.
148
+
-`null_safety` for null safety checks.
105
149
106
-
-`Testing` for general test checks.
150
+
-`testing` for general test checks.
107
151
108
152
For these groups, the default handler is `cpp2::report_and_terminate`, which prints information about the violation to `std::cerr` and then calls `std::terminate()`. But you can customize it to do anything you want, including to integrate with any third-party or in-house error reporting system your project is already using. For example:
109
153
110
-
```cpp title="Example of customized contract violation handler" hl_lines="2 8-9"
154
+
```cpp title="Example of customized contract violation handler" hl_lines="2 8-9 17"
111
155
main: () -> int = {
112
-
cpp2::Default.set_handler(call_my_framework&);
113
-
assert<Default>(false, "this is a test, this is only a test");
156
+
cpp2::default.set_handler(call_my_framework&);
157
+
assert<default>(false, "this is a test, this is only a test");
114
158
std::cout << "done\n";
115
159
}
116
160
117
161
call_my_framework: (msg: * constchar) = {
118
162
// You can do anything you like here, including arbitrary work
119
-
// and integration with your current error reporting libraries
163
+
// and integration with your current error reporting libraries,
164
+
// log-and-continue, throw an exception, whatever is wanted...
0 commit comments