Support Rust syntax in thrust::predicate bodies - #114
Conversation
9c96cde to
fe79724
Compare
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 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
fe79724 to
6c30a07
Compare
coord-e
left a comment
There was a problem hiding this comment.
全体の設計は妥当だと思います。formula_fn 属性による判別、v{i} パラメータ名と TermVarIdx の対応(FunctionParamIdx → TermVarIdx がインデックス一致、AnnotFnTranslator::build_env_from_params も sig.inputs() と同順で列挙)、TermSortEnv リファクタの Clause ケースでの挙動保存、unbox の新バリアント対応、既存の生 SMT2 述語が is_raw_smt2_body で従来どおり raw 判定される点、いずれも確認しました。cargo check --all-targets は通ります(手元にソルバがないため ui テストは未実行)。
バグ2件とコメントの陳腐化1件をインラインで指摘しています。
Generated by Claude Code
|
|
||
| let formula_fn = self | ||
| .ctx | ||
| .formula_fn_with_args(self.local_def_id, self.tcx.mk_args(&[])) |
There was a problem hiding this comment.
mk_args(&[]) discards the predicate's generic args. For a Rust-bodied predicate declared in a trait default body or inside a generic impl, formula_fn_with_args reaches EarlyBinder::instantiate with an empty arg list and ICEs with "type parameter out of range".
Every other formula_fn_with_args call site (analyze.rs:760/782/859, local_def.rs:837) threads through real generic_args. crate_::placeholder_generic_args (crate_.rs:193) exists for exactly this case — it is currently private, so it would need to be reachable from here.
Generated by Claude Code
| .clone() | ||
| .map_var(|idx| chc::TermVarIdx::from(idx.index())); | ||
|
|
||
| self.ctx.system.borrow_mut().push_pred_define_formula( |
There was a problem hiding this comment.
define-funs are emitted in push order, i.e. source order. A Rust predicate body that calls another predicate declared later in the file therefore emits a define-fun referring to a symbol the solver has not seen yet, and the query is rejected.
This is newly reachable with this PR: AnnotFnTranslator (annot_fn.rs:841-873) now translates predicate calls appearing inside formulas, so a predicate body can reference another predicate. Raw SMT-LIB bodies never produced such a reference, which is why push order was fine until now.
Generated by Claude Code
There was a problem hiding this comment.
The heading comment on expand_predicate (L19-21) is now stale — it states that predicate bodies are consumed as raw SMT-LIB string literals and "are not routed through formula!", which the formula_fn branch added below directly contradicts.
Generated by Claude Code
Summary
#[thrust::predicate]bodies can now be written as ordinary Rust boolean expressions instead of raw SMT-LIB2 string literals, reusing the existingformula_fntranslation pipeline (AnnotFnTranslator).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:AnnotFnTranslatorthrough the existingformula_fnscache (formula_fn_with_args), exactly likerequires/ensures/invariant. The resultingchc::Formulais emitted as the predicate's SMTdefine-fun.The macro picks which by inspecting the body (string-literal statement ⇒ raw; otherwise Rust). Borrow-check skipping is free, since
mir_borrowck_skip_formula_fnalready keys on theformula_fnattribute. Predicate call sites are still resolved as namedUserDefinedPredatoms.Changes
chc.rs—UserDefinedPredDefbody becomes aUserDefinedPredBody { Raw, Formula }enum; addpush_pred_define_formula. NewTermSortEnvtrait (var_sort/term_sort), implemented forClauseandIndexVec<TermVarIdx, Sort>.chc/smtlib2.rs— theTerm/Atom/Formula/BodyDisplay wrappers now hold&dyn TermSortEnvinstead of&Clause. AFormulapredicate body is rendered by passing the sig's sorts as anIndexVec— no synthetic/fakeClauseis fabricated.chc/format_context.rs— uses theTermSortEnvtrait (movedClause::term_sortonto it).chc/unbox.rs— unboxFormulapredicate bodies.analyze/crate_.rs— register predicate items also markedformula_fn.analyze/local_def.rs—define_as_predicatebranches on theformula_fnattribute; pulls the formula fromformula_fn_with_args, naming paramsv{i}.thrust-macros/src/spec.rs—expand_predicate: detect raw-vs-Rust body; for Rust bodies add#[thrust::formula_fn], rewriteself→self_, add allow-attrs.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
main(single feature commit).iterators/fixed_filter_loop_none.rs, which returns solverunknownon z3 4.13.0. That failure is pre-existing and unrelated — it reproduces identically onmainat the rebase base with the same z3, andmainpins 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.self.x * 2 == doubled.xlowers 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