Skip to content

Commit 624e9e9

Browse files
coord-eclaude
andauthored
docs: update annotation documentation for the new macro-based syntax (#111)
* docs: update annotation documentation for the new macro-based syntax Replace the legacy `#[thrust::requires]`/`ensures`/`param`/`ret` annotation syntax in the README with the current `thrust_macros::*` surface syntax, and document the rest of the current annotation system: mutable-reference models (`*ma`/`!ma`/`Mut::new`), refinement types and `sig`, trusted/callable functions, predicates, existentials, loop invariants, method/trait annotations via `context`, higher-order `pre!`/ `post!`, and the `thrust_models` model types. Also refresh the Development section to describe the thrust-macros expansion pipeline. Refs #70 https://claude.ai/code/session_019Z1nUnqp3EeceJMohsNTqe * docs: trim annotation docs and restore callable description Drop the Predicates/Existentials/Loop invariants/Methods/Higher-order/ Model types subsections for now, and restore the original nuance that `#[thrust::callable]` is an alias for requiring and ensuring `true`. Refs #70 https://claude.ai/code/session_019Z1nUnqp3EeceJMohsNTqe * docs: restore original trusted/callable example trusted and callable are current plugin attributes, not legacy syntax, so revert to the original rand() example and wording; only the requires/ensures alias paths needed updating. Refs #70 https://claude.ai/code/session_019Z1nUnqp3EeceJMohsNTqe --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent fc7ebfa commit 624e9e9

1 file changed

Lines changed: 32 additions & 12 deletions

File tree

README.md

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,11 @@ Integration test examples are located under `tests/ui/` and can be executed usin
6868

6969
## Annotation
7070

71-
Thrust can verify a wide range of programs without explicit annotations, but you can use `#[thrust::requires(expr)]` and `#[thrust::ensures(expr)]` to annotate the precondition and postcondition of a function, aiding in verification or specifying the specification. Here, `expr` is a logical expression that supports basic integer, boolean, and reference operations.
71+
Thrust can verify a wide range of programs without explicit annotations, but you can use `#[thrust_macros::requires(expr)]` and `#[thrust_macros::ensures(expr)]` to annotate the precondition and postcondition of a function, aiding in verification or specifying the intended behavior. Here, `expr` is an ordinary Rust expression that Thrust interprets as a logical formula. It supports the usual integer, boolean, and comparison operators, calls to functions declared with `#[thrust_macros::predicate]`, and the model operations described below.
7272

7373
```rust
74-
#[thrust::requires(n >= 0)]
75-
#[thrust::ensures((result * 2) == n * (n + 1))]
74+
#[thrust_macros::requires(n >= 0)]
75+
#[thrust_macros::ensures((result * 2) == n * (n + 1))]
7676
fn sum(n: i32) -> i32 {
7777
if n == 0 {
7878
0
@@ -82,21 +82,29 @@ fn sum(n: i32) -> i32 {
8282
}
8383
```
8484

85-
You can use the `^` unary operator to denote the value of a mutable reference after the function is called. Note that in the current implementation, you need to specify both `requires` and `ensures` when using either one.
85+
In an `ensures` expression, the special identifier `result` refers to the return value of the function. `requires` and `ensures` are independent: you can write either one on its own, and a missing one defaults to `true`.
86+
87+
### Mutable references
88+
89+
Within an annotation, a mutable reference `ma: &mut T` is modeled by its value at the time the function is called and its value when the function returns (the *prophecy* value). Use the deref operator `*ma` to denote the current value and the unary `!ma` to denote the final value. You can also construct a mutable-reference model directly with `thrust_models::model::Mut::new(current, final)`.
8690

8791
```rust
88-
#[thrust::requires(true)]
89-
#[thrust::ensures(^ma == *ma + a)]
92+
use thrust_models::model::Mut;
93+
94+
// `add` increments the referent by `a`. Equivalently, `!ma == *ma + a`.
95+
#[thrust_macros::ensures(ma == Mut::new(*ma, *ma + a))]
9096
fn add(ma: &mut i32, a: i32) {
9197
*ma += a;
9298
}
9399
```
94100

95-
The conditions on `thrust::requires` and `thrust::ensures` are internally encoded as refinement types for the parameter and return types. You can also specify these refinement types directly using `#[thrust::param(param: type)]` and `#[thrust::ret(type)]`.
101+
### Refinement types
102+
103+
The conditions on `requires`/`ensures` are internally encoded as refinement types of the parameter and return types. You can also specify these refinement types directly. A refinement type is written `{ binder: type | formula }`, where `type` is a Rust type and `formula` constrains the value bound to `binder`. Use `#[thrust_macros::param(name: type)]` for a parameter and `#[thrust_macros::ret(type)]` for the return value:
96104

97105
```rust
98-
#[thrust::param(n: { x: int | x >= 0 })]
99-
#[thrust::ret({ x: int | (x * 2) == n * (n + 1) })]
106+
#[thrust_macros::param(n: { x: i32 | x >= 0 })]
107+
#[thrust_macros::ret({ x: i32 | (x * 2) == n * (n + 1) })]
100108
fn sum(n: i32) -> i32 {
101109
if n == 0 {
102110
0
@@ -106,7 +114,18 @@ fn sum(n: i32) -> i32 {
106114
}
107115
```
108116

109-
The bodies of functions marked with `#[thrust::trusted]` are not analyzed by Thrust. Additionally, `#[thrust::callable]` is an alias for `#[thrust::requires(true)]` and `#[thrust::ensures(true)]`.
117+
`#[thrust_macros::sig(..)]` is a shorthand that combines the parameter and return refinements into a single function-signature-shaped annotation:
118+
119+
```rust
120+
#[thrust_macros::sig(fn(x: { v: i32 | v > 0 }) -> { r: i32 | r >= x })]
121+
fn g(x: i32) -> i32 {
122+
x
123+
}
124+
```
125+
126+
Refinements may be nested inside generic arguments and reference types, e.g. `Box<{ v: i64 | v > 0 }>` or `&mut { v: i32 | v >= 0 }`.
127+
128+
The bodies of functions marked with `#[thrust::trusted]` are not analyzed by Thrust. Additionally, `#[thrust::callable]` is an alias for `#[thrust_macros::requires(true)]` and `#[thrust_macros::ensures(true)]`.
110129

111130
```rust
112131
#[thrust::trusted]
@@ -128,11 +147,12 @@ Several environment variables are used by Thrust to configure its behavior:
128147

129148
The implementation of the Thrust is largely divided into the following modules.
130149

131-
- `analyze`: MIR analysis. Further divided into the modules corresponding to the program units: `analyze::crate_`, `analyze::local_def`, and `analyze::basic_block`.
150+
- `analyze`: MIR analysis. Further divided into the modules corresponding to the program units: `analyze::crate_`, `analyze::local_def`, and `analyze::basic_block`. `analyze::annot` and `analyze::annot_fn` translate the lowered annotation attributes into refinement types and formulas.
132151
- `refine`: Typing environment and related implementations.
133152
- `rty`: Refinement type primitives.
134153
- `chc`: CHC and logic primitives, and it also implements an invocation of the underlying CHC solver.
135-
- `annot`: Refinement type annotation parser.
154+
155+
The surface annotation syntax (`#[thrust_macros::requires]`, `ensures`, `param`, `ret`, `sig`, `predicate`, `invariant!`, `pre!`/`post!`, …) is implemented in the `thrust-macros` crate. These procedural macros expand into a small set of internal plugin attributes — chiefly `#[thrust::formula_fn]` companion functions (written as ordinary Rust over the `thrust_models` model types) together with `#[thrust::requires_path]`, `#[thrust::ensures_path]`, and `#[thrust::refinement_path(..)]` markers that link those companions to the function being specified. `analyze::annot_fn` then reads the type-checked HIR of each `formula_fn` to build the corresponding `chc::Formula`/`rty::Refinement`. The model types themselves are declared in `std.rs`, which Thrust injects into every crate it analyzes.
136156

137157
The implementation generates subtyping constraints in the form of CHCs (`chc::System`). The entry point is `analyze::crate_::Analyzer::run`, followed by `analyze::local_def::Analyzer::run` and `analyze::basic_block::Analyzer::run`, while accumulating the necessary information in `analyze::Analyzer`. Once `chc::System` is collected for the entire input, it invokes an external CHC solver via the `chc::solver` module and subsequently reports the result.
138158

0 commit comments

Comments
 (0)