Skip to content

Commit fe79724

Browse files
committed
Support Rust syntax in thrust::predicate bodies
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
1 parent af7cd99 commit fe79724

13 files changed

Lines changed: 266 additions & 140 deletions

File tree

src/analyze/crate_.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,17 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> {
8989
}
9090

9191
if analyzer.is_annotated_as_predicate() {
92-
analyzer.analyze_predicate_definition();
92+
// A predicate whose body is a Rust expression is also marked
93+
// `formula_fn`; register it first so `analyze_predicate_definition`
94+
// can pull the translated formula via `formula_fn_with_args`.
95+
let is_formula_fn = analyzer.is_annotated_as_formula_fn();
96+
drop(analyzer);
97+
if is_formula_fn {
98+
self.ctx.register_formula_fn(local_def_id);
99+
}
100+
self.ctx
101+
.local_def_analyzer(local_def_id)
102+
.analyze_predicate_definition();
93103
self.skip_analysis.insert(local_def_id);
94104
return;
95105
}

src/analyze/local_def.rs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,40 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> {
6060
}
6161

6262
fn define_as_predicate(&self, pred: chc::UserDefinedPred) {
63+
let sig = self.ctx.fn_sig(self.local_def_id.to_def_id());
64+
let arg_sorts = sig
65+
.inputs()
66+
.iter()
67+
.map(|input_ty| self.type_builder.build(*input_ty).to_sort());
68+
69+
// A predicate marked `formula_fn` carries a Rust-expression body that has
70+
// been translated into a `chc::Formula`; otherwise the body is a raw
71+
// SMT-LIB2 string literal.
72+
if self.is_annotated_as_formula_fn() {
73+
// Name the parameters `v{i}` to match how `chc::TermVarIdx` renders the
74+
// formula's variables (see `chc::UserDefinedPredBody::Formula`).
75+
let arg_name_and_sorts = arg_sorts
76+
.enumerate()
77+
.map(|(i, sort)| (format!("v{i}"), sort))
78+
.collect::<Vec<_>>();
79+
80+
let formula_fn = self
81+
.ctx
82+
.formula_fn_with_args(self.local_def_id, self.tcx.mk_args(&[]))
83+
.expect("predicate formula function is not registered");
84+
let formula = formula_fn
85+
.formula()
86+
.clone()
87+
.map_var(|idx| chc::TermVarIdx::from(idx.index()));
88+
89+
self.ctx.system.borrow_mut().push_pred_define_formula(
90+
pred,
91+
chc::UserDefinedPredSig::from(arg_name_and_sorts),
92+
formula,
93+
);
94+
return;
95+
}
96+
6397
// function's body
6498
use rustc_hir::{Block, Expr, ExprKind};
6599

@@ -88,12 +122,6 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> {
88122
.to_string()
89123
});
90124

91-
let sig = self.ctx.fn_sig(self.local_def_id.to_def_id());
92-
let arg_sorts = sig
93-
.inputs()
94-
.iter()
95-
.map(|input_ty| self.type_builder.build(*input_ty).to_sort());
96-
97125
let arg_name_and_sorts = arg_names.into_iter().zip(arg_sorts).collect::<Vec<_>>();
98126

99127
self.ctx.system.borrow_mut().push_pred_define(

src/chc.rs

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1918,9 +1918,32 @@ impl Clause {
19181918
pub fn is_nop(&self) -> bool {
19191919
self.head.is_top() || self.body.is_bottom()
19201920
}
1921+
}
1922+
1923+
/// Resolves the sort of term-level variables.
1924+
///
1925+
/// Rendering a [`Term`] to SMT-LIB2 needs the sort of each variable, because
1926+
/// box/mut/tuple constructors and selectors are sort-indexed. Anything that can
1927+
/// provide variable sorts (a [`Clause`] via its `vars`, or a bare list of sorts
1928+
/// for a `define-fun` signature) can drive term rendering through this trait,
1929+
/// so callers need not fabricate a [`Clause`].
1930+
pub trait TermSortEnv {
1931+
fn var_sort(&self, var: TermVarIdx) -> Sort;
19211932

19221933
fn term_sort(&self, term: &Term<TermVarIdx>) -> Sort {
1923-
term.sort(|v| self.vars[*v].clone())
1934+
term.sort(|v| self.var_sort(*v))
1935+
}
1936+
}
1937+
1938+
impl TermSortEnv for Clause {
1939+
fn var_sort(&self, var: TermVarIdx) -> Sort {
1940+
self.vars[var].clone()
1941+
}
1942+
}
1943+
1944+
impl TermSortEnv for IndexVec<TermVarIdx, Sort> {
1945+
fn var_sort(&self, var: TermVarIdx) -> Sort {
1946+
self[var].clone()
19241947
}
19251948
}
19261949

@@ -1980,11 +2003,22 @@ pub struct PredVarDef {
19802003

19812004
pub type UserDefinedPredSig = Vec<(String, Sort)>;
19822005

2006+
/// The body of a user-defined predicate.
2007+
///
2008+
/// A predicate can be defined either by a raw SMT-LIB2 string (inserted into the
2009+
/// generated `define-fun` verbatim) or by a [`Formula`] translated from a Rust
2010+
/// expression via the `formula_fn` infrastructure.
2011+
#[derive(Debug, Clone)]
2012+
pub enum UserDefinedPredBody {
2013+
Raw(String),
2014+
Formula(Formula<TermVarIdx>),
2015+
}
2016+
19832017
#[derive(Debug, Clone)]
19842018
pub struct UserDefinedPredDef {
19852019
symbol: UserDefinedPred,
19862020
sig: UserDefinedPredSig,
1987-
body: String,
2021+
body: UserDefinedPredBody,
19882022
}
19892023

19902024
/// A CHC system.
@@ -2012,8 +2046,24 @@ impl System {
20122046
sig: UserDefinedPredSig,
20132047
body: String,
20142048
) {
2015-
self.user_defined_pred_defs
2016-
.push(UserDefinedPredDef { symbol, sig, body })
2049+
self.user_defined_pred_defs.push(UserDefinedPredDef {
2050+
symbol,
2051+
sig,
2052+
body: UserDefinedPredBody::Raw(body),
2053+
})
2054+
}
2055+
2056+
pub fn push_pred_define_formula(
2057+
&mut self,
2058+
symbol: UserDefinedPred,
2059+
sig: UserDefinedPredSig,
2060+
formula: Formula<TermVarIdx>,
2061+
) {
2062+
self.user_defined_pred_defs.push(UserDefinedPredDef {
2063+
symbol,
2064+
sig,
2065+
body: UserDefinedPredBody::Formula(formula),
2066+
})
20172067
}
20182068

20192069
pub fn push_clause(&mut self, clause: Clause) -> Option<ClauseId> {

src/chc/format_context.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
88
use std::collections::BTreeSet;
99

10-
use crate::chc::{self, hoice::HoiceDatatypeRenamer};
10+
use crate::chc::{self, hoice::HoiceDatatypeRenamer, TermSortEnv as _};
1111

1212
/// A context for formatting a CHC system.
1313
///

0 commit comments

Comments
 (0)