Phase 1: Seq<T> ghost type#143
Draft
coord-e wants to merge 4 commits into
Draft
Conversation
Implements minimal Phase 1 of the Creusot-style ghost sequence support: defines `thrust_models::model::Seq<T>` as `(Array<Int, T>, Int)` (mirroring the existing Vec model) and wires per-operation analyzer arms in `annot_fn.rs` so calls like `s.len()` and `s.push(e)` lower mechanically to tuple projections / array stores. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A7nMxM1sJ8wxVJmMvsEvSX
In ExprKind::Index, detect a `model::Seq<_>` receiver and lower `s[i]` to `select(s.0, i)` instead of treating the receiver as an Array directly. Introduces the `#[thrust::def::seq_model]` marker and `DefIdCache::seq_model()` to recognise the Seq ADT. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A7nMxM1sJ8wxVJmMvsEvSX
Adds the remaining Seq operations beyond `len` / `push` / indexing: - `Seq::empty()` / `Seq::singleton(x)` — lower to tuple values whose array slot is the new global `seq_default_arr_int` constant (declared in the SMT preamble) and whose length is `0` / `1`. The array contents are irrelevant because indices ≥ length are never accessed. - `s.concat(t)` / `s.subsequence(l, r)` — lower to `Tuple([..., len])` where `len` is computed inline (`a.len + b.len` / `r - l`). The array slot uses fresh uninterpreted symbols `seq_concat_arr_int` / `seq_subseq_arr_int`, declared on demand via new `System` flags `uses_seq_concat` / `uses_seq_subseq` to avoid tripping solvers that can't synthesize their sort even when unused. The extensional pointwise axioms describing index access on the result are intentionally omitted — they cause Spacer to return `unknown` even on clauses that don't touch the axiomatized positions. As a result this round supports length-only reasoning on `concat` / `subsequence`; indexed reasoning is a follow-up. Two peephole simplifications in `chc::Term` keep the encoding inside what HORN solvers reliably reduce: - `tuple_proj(tuple(ts...), i)` ↦ `ts[i]` — needed so that `Seq::empty().len()` reduces syntactically to `0` (Spacer doesn't reduce datatype projections of literal constructors). - `select(store(a, i, v), j)` ↦ `v` / `select(a, j)` when both indices are concrete integer literals — needed for `Seq::singleton(x)[0] == x`. Also fixes `chc::Term::App` emission to print bare `f` (not `(f)`) for nullary functions, matching SMT-LIB conventions for `declare-const`. Adds paired pass/fail UI tests for each new operation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A7nMxM1sJ8wxVJmMvsEvSX
Per-element semantics of `concat` / `subsequence` now verify under
pcsat — previously only length-only reasoning was supported.
Approach: keep the array-returning `seq_concat_arr_int` /
`seq_subseq_arr_int` symbols as *peephole markers* in the array slot
of concat / subsequence results, and add a `select` simplification
rule that rewrites `select(seq_concat_arr_int(a, la, b), i)` to a new
element-getter `seq_concat_get_int(a, la, b, i)` (and analogously for
subsequence) before the term reaches the SMT solver. The getters are
emitted via SMT-LIB2 `(define-fun-rec ...)` whenever they're used.
pcsat parses `define-fun-rec` returning `Int` and reasons about it;
z3 / Spacer in HORN logic still returns `unknown` for these
definitions, so indexed-access tests opt into pcsat via
`THRUST_SOLVER=tests/thrust-pcsat-wrapper`. Length-only reasoning
continues to work under either solver (the length is computed inline
by the analyzer as `a.len + b.len` / `r - l`).
Paired pass/fail tests added: `seq_concat_index{,_right}`,
`seq_subseq_index`.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A7nMxM1sJ8wxVJmMvsEvSX
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.
Summary
Phase 1 of the Creusot-style ghost-sequence support tracked in
/root/.claude/plans/how-can-we-implement-buzzing-cascade.md. Eventualgoal: verify Creusot's
iter::Map/iter::MapExtin Thrust.Introduces
thrust_models::model::Seq<T>as(Array<Int, T>, Int)(mirroring the existing Vec model) and wires the following operations,
each with paired
pass/failUI tests:s.len()tuple_proj(s, 1)s.push(e)tuple([store(s.0, s.1, e), s.1 + 1])s[i]select(s.0, i)— detected via#[thrust::def::seq_model]markerSeq::<T>::empty()tuple([seq_default_arr_int, 0])Seq::singleton(x)tuple([store(seq_default_arr_int, 0, x), 1])s.concat(t)tuple([seq_concat_arr_int(s.0, s.1, t.0), s.1 + t.1])s.subsequence(l, r)tuple([seq_subseq_arr_int(s.0, l), r - l])Solver-side details
seq_default_arr_intis declared once in the SMT preamble; the arraycontents are irrelevant because indices ≥ length are never accessed.
seq_concat_arr_int(a, la, b)/seq_subseq_arr_int(a, l)arepeephole markers: the
selectsimplifier rewritesselect(seq_concat_arr_int(a, la, b), i)→ element-getterseq_concat_get_int(a, la, b, i)(and similarly for subseq) beforethe term reaches the SMT solver. The getters are emitted via SMT-LIB2
(define-fun-rec ...)whenever used, on demand (gated bySystem::uses_seq_concat/uses_seq_subseq).concat/subsequenceworks under eithersolver, because the length is computed inline by the analyzer
(
a.len + b.len,r - l).s.concat(t)[i] == s[i]wheni < s.len()) works under pcsat — opt in with//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper. z3 / Spacerin HORN logic returns
unknownfordefine-fun-recover arrays,which is a known solver limitation.
The marker symbols themselves are never declared and should not reach
the SMT output; if they do (e.g. through a whole-array equality), the
SMT solver reports an undeclared symbol. The reference iterator
examples never compare array slots directly, so this is not expected to
block the eventual port.
Term simplifiers added to
chc::TermTwo small peephole rules keep the encoding inside what HORN solvers
reliably reduce:
tuple_proj(tuple(ts...), i)↦ts[i]— Spacer doesn't reducedatatype projections of literal constructors, so this is needed for
e.g.
Seq::empty().len()to reduce syntactically to0.select(store(a, i, v), j)↦v/select(a, j)for concretei,jinteger literals — needed forSeq::singleton(x)[0] == x.Plus the marker-consuming
select(seq_*_arr_int(...), i)rewritesdescribed above.
Additionally fixes
chc::Term::Appemission to print baref(not(f)) for nullary functions, matching SMT-LIB conventions fordeclare-const.Phases that follow (separate PRs)
snapshot!{}/proof_assert!{}macros#[predicate]propagationmap.rs/map_ext.rsTest plan
cargo buildcargo fmt --all -- --checkcargo clippy -- -D warningscargo test(270 / 270 passing, including 12 new pairedseq_*tests:seq_len_push,seq_index,seq_empty,seq_singleton,seq_concat,seq_subsequence,seq_concat_index,seq_concat_index_right,seq_subseq_index)🤖 Generated with Claude Code
https://claude.ai/code/session_01A7nMxM1sJ8wxVJmMvsEvSX