Skip to content

Support Rust syntax in thrust::predicate bodies#114

Draft
coord-e wants to merge 1 commit into
mainfrom
claude/tender-ramanujan-F2ISx
Draft

Support Rust syntax in thrust::predicate bodies#114
coord-e wants to merge 1 commit into
mainfrom
claude/tender-ramanujan-F2ISx

Conversation

@coord-e

@coord-e coord-e commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Summary

#[thrust::predicate] bodies can now be written as ordinary Rust boolean expressions instead of raw SMT-LIB2 string literals, reusing the existing formula_fn translation pipeline (AnnotFnTranslator).

// before — raw SMT2
#[thrust_macros::predicate]
fn is_double(self, doubled: Self) -> bool {
    "(= (* (tuple_proj<Int>.0 self_) 2) (tuple_proj<Int>.0 doubled))"; true
}

// after — Rust syntax
#[thrust_macros::predicate]
fn is_double(self, doubled: Self) -> bool {
    self.x * 2 == doubled.x
}

Raw SMT2 string bodies still work unchanged (backward compatible).

How it works

A predicate written with #[thrust_macros::predicate] whose body is a Rust expression is expanded with an additional #[thrust::formula_fn] attribute, which is the single discriminator:

  • present ⇒ the body is interpreted by AnnotFnTranslator through the existing formula_fns cache (formula_fn_with_args), exactly like requires/ensures/invariant. The resulting chc::Formula is emitted as the predicate's SMT define-fun.
  • absent ⇒ the legacy raw-SMT2 string path (unchanged).

The macro picks which by inspecting the body (string-literal statement ⇒ raw; otherwise Rust). Borrow-check skipping is free, since mir_borrowck_skip_formula_fn already keys on the formula_fn attribute. Predicate call sites are still resolved as named UserDefinedPred atoms.

Changes

  • chc.rsUserDefinedPredDef body becomes a UserDefinedPredBody { Raw, Formula } enum; add push_pred_define_formula. New TermSortEnv trait (var_sort/term_sort), implemented for Clause and IndexVec<TermVarIdx, Sort>.
  • chc/smtlib2.rs — the Term/Atom/Formula/Body Display wrappers now hold &dyn TermSortEnv instead of &Clause. A Formula predicate body is rendered by passing the sig's sorts as an IndexVecno synthetic/fake Clause is fabricated.
  • chc/format_context.rs — uses the TermSortEnv trait (moved Clause::term_sort onto it).
  • chc/unbox.rs — unbox Formula predicate bodies.
  • analyze/crate_.rs — register predicate items also marked formula_fn.
  • analyze/local_def.rsdefine_as_predicate branches on the formula_fn attribute; pulls the formula from formula_fn_with_args, naming params v{i}.
  • thrust-macros/src/spec.rsexpand_predicate: detect raw-vs-Rust body; for Rust bodies add #[thrust::formula_fn], rewrite selfself_, add allow-attrs.
  • tests — scalar (annot_preds), trait (annot_preds_trait), and multi-field (annot_preds_trait_multi) predicate tests (pass + fail) converted to Rust syntax.

Dependency

Named struct-field access (self.x) in the trait/multi-field predicates relies on #118 (merged), which taught the formula translator to resolve named ADT fields.

Notes

  • Rebased onto the latest main (single feature commit).
  • Verification (local, z3 4.13.0 + docker pcsat): the full ui suite passes except iterators/fixed_filter_loop_none.rs, which returns solver unknown on z3 4.13.0. That failure is pre-existing and unrelated — it reproduces identically on main at the rebase base with the same z3, and main pins z3 to 4.15.4 (ci: pin Z3 to 4.15.4, the latest version without mut_recursive timeout #149) precisely to avoid such solver-version issues. CI runs 4.15.4.
  • Inspected the generated SMT2: self.x * 2 == doubled.x lowers to exactly (= (* (tuple_proj<Int>.0 v0) 2) (tuple_proj<Int>.0 v1)) — identical to the previous raw form.

https://claude.ai/code/session_01WdLyxyy4ieAxrexj83X5MX

Predicate bodies can be written as ordinary Rust boolean expressions instead
of raw SMT-LIB2 string literals, reusing the formula_fn translation pipeline.

A predicate whose body is a Rust expression is emitted with an additional
#[thrust::formula_fn] attribute; that attribute is the discriminator. When
present, the body is translated through the formula_fns cache and the
resulting chc::Formula is emitted as the predicate's SMT define-fun. Raw SMT2
string bodies still work unchanged.

Rendering of a translated predicate formula goes through a new TermSortEnv
trait (implemented for Clause and IndexVec<TermVarIdx, Sort>) instead of a
fabricated Clause, so the smtlib2 Display wrappers hold &dyn TermSortEnv.

Trait/struct predicates use named field access (self.x), relying on the
named-field resolution already in main (#118).

https://claude.ai/code/session_01WdLyxyy4ieAxrexj83X5MX
@coord-e coord-e force-pushed the claude/tender-ramanujan-F2ISx branch from 9c96cde to fe79724 Compare July 8, 2026 11:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants