Cleanup prusti-contracts, add/implement all Viper builtins - #188
Merged
Conversation
JonasAlaif
force-pushed
the
prusti-builtin
branch
from
July 13, 2026 19:45
eb9fd8d to
3791c89
Compare
JonasAlaif
force-pushed
the
prusti-builtin
branch
2 times, most recently
from
July 14, 2026 14:15
efabc52 to
ff97c61
Compare
JonasAlaif
force-pushed
the
prusti-builtin
branch
from
July 14, 2026 15:13
ff97c61 to
a86f309
Compare
JonasAlaif
marked this pull request as ready for review
July 19, 2026 17:53
JonasAlaif
force-pushed
the
prusti-builtin
branch
2 times, most recently
from
July 19, 2026 22:57
56e3982 to
46766c3
Compare
JonasAlaif
force-pushed
the
prusti-builtin
branch
from
July 19, 2026 23:00
46766c3 to
da0aaab
Compare
Aurel300
reviewed
Jul 20, 2026
JonasAlaif
force-pushed
the
prusti-builtin
branch
from
July 21, 2026 09:59
156ad70 to
40e01ae
Compare
ysaleh03
pushed a commit
to ysaleh03/prusti-dev
that referenced
this pull request
Jul 27, 2026
…0#188) * Cleanup `prusti-contracts`, add/implement all Viper builtins * Cleanup and extend `expr!` macro * Make ghost types executable * Skip span in key on pure builtin use * Better handling of ghost blocks * Revert `ImpureEncVisitor::new` and fmt * Review changes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Native Viper collections, executable ghost types, and reworked
ghost!blocksThis PR completes the
prusti_contractsghost types (Seq,Set,Multiset,Map,Ghost, alongside the existingInt/Real): the collections are now encoded as thecorresponding native Viper collection types, ghost types can be used in executable
(impure) code, and
ghost!blocks are redesigned to be value-producing, codegen-free, andverified inline — in both impure and pure code.
prusti-contracts: one API surface, safe to executeThe ghost types are rewritten as zero-sized types in a
private_sharedmodule compiled underboth the
prustiand non-prustifeatures. The split is by executability: everything inthe shared module has real (non-panicking) bodies and is safe to run, while operations only
available under the
prustiflag are the ones whose use in executable code is rejected witha verification error (rather than ever reaching their panicking bodies at runtime).
Seq<T>/Set<T>/Multiset<T>/Map<K, V>with a uniform API (new,single,append/union,update,insert,intersection,difference,is_subset,contains,len,keys,values,setminus) and theseq!/set!/multiset!/map!literal macros. A
Value<T>helper trait lets elements be passed asTor&T.seq[i]andmap[k]returnGhost<T>(deref for the value);seq[a..b],seq[a..],seq[..b]return subsequences.PartialEqon all ghost types is snapshot equality;a === bnow desugars toGhost::new_ref(&a) == Ghost::new_ref(&b), replacing the removedsnapshot_equalityandsnapfunctions. The float classification helpers get real bodies.VIR: native collection support
TypeKindgainsSeq/Multiset/MapalongsideSet, with new expression nodes:CollectionBinOp(contains/union/intersection/difference/subset/concat/index/take/drop —separate from
BinOpKindbecause the operands are heterogeneously typed and the operation isselected by the collection operand's type),
CollectionLiteral,CollectionUpdate(
x[k := v]),CollectionLen,MapDomain, andMapRange; plus statically-typed,type-checked
mk_*constructors andexpr!macro syntax for the new operations(
(x) in (c),(a) setminus (b),(base)[index],|c|,domain(m),!(b)).Encoder: operand-based builtin encoding
PrustiBuiltinclassifies calls intoprusti_contracts(attributing trait-method calls viathe resolved impl, so e.g.
PartialOrd::leonIntis recognized even though the defaultbody lives in
core). All operand-based builtins are encoded once per (op, generics) byPrustiBuiltinEncas a cached snapshot expression with one hole per operand, shared by thepure and impure encoders. The collections are encoded at their most generic instantiation
(
s_Paramelements) with casters applied at use sites.Partial operations (
s[i],s[i := v],m[k], slicing) are encoded by context:verification condition with a dedicated message ("the sequence index may be out of
bounds", "the map may not contain this key", …), matching Rust's panicking semantics.
CollectionOpsEnc), axiomatized toagree with the native operation on their domain — precondition-free
f_functions couldnever discharge the obligation.
Ghost types are now allowed in executable code: construction, arithmetic, collection
updates etc. encode as snapshot assignments. Spec-only operations (comparisons yielding
bool,Ghostderef,contains,is_subset, …) remain rejected in executable code —except inside ghost blocks (below).
ghost!blocks, redesignedghost! { body }now expands to:never-called
Fnclosure, so the compiler enforces ghost discipline: no mutation,mutable borrowing, or consumption of outer variables (rebind instead:
let x = ghost! { .. }).ghost_call's signature unifies the two copies' types, and theif/elsegivesghost_erased()the body's type — solet x = ghost! { .. }binds aGhost<T>inferable from either side.if false, so it is pruned before codegen — the runtimeeffect of a ghost block is exactly
Ghost(PhantomData).GhostBlocks, keyed off the single-blockghost_erasedstand-in arm) and encode the switch as an unconditional jump into the ghostarm; within the ghost region (blocks dominated by the arm entry) spec-only builtins are
allowed.
references in scope, call pure and impure (contract-checked) functions, branch, loop
(with
body_invariant!), containprusti_assert!s, and nest.ghost!also works inpure functions.
return,break,continue, and now?) with"Can't leave the ghost block early", while correctly allowing them inside closures/items
nested in the body. The old
ghost_begin/ghost_endmarker machinery is removedthroughout.
Other encoder changes
ImpureEncVisitor::visit_bodywalks blocks in reverse-postorder instead of BFS, soboth branches are always encoded before their join; a new
wandless_callsset correctlyexempts all non-method-call terminators (pure calls, intrinsics, builtins) from wand
application on borrow expiry.
method to a contract-only stub.
assertion's closure); regression test:
quick/forall-in-assert.rs.(needed for the checker closures).
Tests
verify/pass/ghost-types/suites:int,sequences,sets,multisets,maps,unsized,ordering, extendedghost/impure, andblocks(29 items covering valuebinding, spec-only ops in ghost code, capture/flow, calls, loops with invariants,
nesting, generics, inference through the binding, pure-code ghost blocks).
verify/fail/ghost-types/suites:bounds(checked collection ops),blocks(failing asserts in ghost code, ghost values are constrained, contract violations,
panicking bodies),
captures(theFncapture discipline).core_proofghost tests and counterexample UI tests migrated to the new API.Breaking changes
Seq::empty/concat/lookup→Seq::new/append/indexing (viaGhost);Map::empty/lookup→Map::new/indexing;snapshot_equality/snapremoved (===still works);
DerefMut for Ghostremoved.ghost!block no longer compiles; rebind the resultinstead (
let x = ghost! { .. };).