Skip to content

Commit a3b6813

Browse files
committed
feat(types): wire backend consumers + fix z3 build + outcome module
Step 3 of the native type-system plan: wire backends to consume TypeInfo decorations, extend GNN graph awareness, and fix a pre-existing build break. Changes: 1. Fix pre-existing z3 build break: - Create `src/rust/provers/outcome.rs` with `ProverOutcome` enum (8 variants: Proved, InconsistentPremises, NoProofFound, UnsupportedFeature, InvalidInput, Timeout, ProverError, SystemError) - Move orphan `check` method from `impl ProverBackend for Z3Backend` to its own `impl Z3Backend` block — it was not a trait member - Result: `cargo check --lib` now passes cleanly (0 errors, 0 warnings) 2. Idris2 QTT multiplicity wiring: - `export()` now reads `Definition.type_info.multiplicity` and emits QTT annotations (`0 `, `1 `, or unrestricted) on type signatures - Added `multiplicity_to_idris2()` helper mapping all Multiplicity variants to Idris2 QTT syntax - Removed duplicate local Multiplicity enum (now uses crate::types) 3. F* effect + refinement wiring: - `to_input_format()` now reads `Definition.type_info.effects` and emits F* effect prefixes (Tot, IO, ST, Exn, Div, GTot, ALL, Custom) - Reads `type_info.refinement` and emits `{v:T | P}` syntax - Added `effect_to_fstar()` helper 4. Dedukti exchange layer Sigma support: - `term_to_dedukti()` renders `Term::Sigma` as `dk_sigma A (x => B)` - `parse_dedukti_term()` parses `dk_sigma` back to `Term::Sigma` 5. GNN graph type-info awareness: - Added `EdgeKind::HasMultiplicity`, `HasEffect`, `HasModality` - `add_type_info_edges()` creates annotation nodes and edges from hypothesis/definition type_info decorations - Embeddings: added "sigma" label detection in feature extraction, updated quantifier feature to include sigma binders Test results: 587 passed, 0 failed, 0 clippy warnings. https://claude.ai/code/session_01FYkVX52Tdn6Arp9dWfPLxq
1 parent 26b96ad commit a3b6813

8 files changed

Lines changed: 429 additions & 189 deletions

File tree

src/rust/exchange/dedukti.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,18 @@ impl DeduktiExporter {
211211
Self::term_to_dedukti(body)
212212
)
213213
},
214+
Term::Sigma {
215+
param,
216+
param_type,
217+
body,
218+
} => {
219+
format!(
220+
"(dk_sigma {} ({} => {}))",
221+
Self::term_to_dedukti(param_type),
222+
param,
223+
Self::term_to_dedukti(body)
224+
)
225+
},
214226
Term::Type(level) => format!("Type {}", level),
215227
Term::Sort(level) => format!("Sort {}", level),
216228
Term::Universe(level) => format!("Type {}", level),
@@ -224,6 +236,32 @@ impl DeduktiExporter {
224236
if trimmed.starts_with('(') && trimmed.ends_with(')') {
225237
// Unwrap parentheses and recurse
226238
Self::dedukti_to_term(&trimmed[1..trimmed.len() - 1])
239+
} else if trimmed.starts_with("dk_sigma ") {
240+
// Sigma type: dk_sigma A (x => B)
241+
let rest = trimmed.trim_start_matches("dk_sigma ").trim();
242+
// Split into the type part and the binder part (x => B)
243+
if let Some(paren_start) = rest.find('(') {
244+
let type_part = rest[..paren_start].trim();
245+
let binder_part = rest[paren_start..].trim();
246+
let inner = if binder_part.starts_with('(') && binder_part.ends_with(')') {
247+
&binder_part[1..binder_part.len() - 1]
248+
} else {
249+
binder_part
250+
};
251+
if let Some(arrow_pos) = inner.find("=>") {
252+
let param = inner[..arrow_pos].trim().to_string();
253+
let body_str = inner[arrow_pos + 2..].trim();
254+
Term::Sigma {
255+
param,
256+
param_type: Box::new(Self::dedukti_to_term(type_part)),
257+
body: Box::new(Self::dedukti_to_term(body_str)),
258+
}
259+
} else {
260+
Term::Const(trimmed.to_string())
261+
}
262+
} else {
263+
Term::Const(trimmed.to_string())
264+
}
227265
} else if trimmed.contains("->") {
228266
// Pi type: A -> B
229267
let parts: Vec<&str> = trimmed.splitn(2, "->").collect();
@@ -416,6 +454,28 @@ mod tests {
416454
assert_eq!(dk, "x");
417455
}
418456

457+
#[test]
458+
fn test_term_to_dedukti_sigma() {
459+
let term = Term::Sigma {
460+
param: "x".to_string(),
461+
param_type: Box::new(Term::Const("Nat".to_string())),
462+
body: Box::new(Term::Const("Prop".to_string())),
463+
};
464+
let dk = DeduktiExporter::term_to_dedukti(&term);
465+
assert!(dk.contains("dk_sigma"), "Sigma should render as dk_sigma, got: {}", dk);
466+
assert!(dk.contains("Nat"), "Sigma param type should appear, got: {}", dk);
467+
}
468+
469+
#[test]
470+
fn test_dedukti_to_term_sigma() {
471+
let dk = "dk_sigma Nat (x => Prop)";
472+
let term = DeduktiExporter::dedukti_to_term(dk);
473+
match term {
474+
Term::Sigma { ref param, .. } => assert_eq!(param, "x"),
475+
_ => panic!("Expected Sigma term, got: {:?}", term),
476+
}
477+
}
478+
419479
#[test]
420480
fn test_import_with_definition() {
421481
let module = DeduktiModule {

src/rust/gnn/embeddings.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,11 @@ impl TermFeatureExtractor {
190190
// Feature [29]: Contains quantifier (forall, exists, pi)
191191
if offset < FEATURE_DIM {
192192
features[offset] = if node.label.contains("pi_")
193+
|| node.label.contains("sigma_")
193194
|| node.label.contains("forall")
194195
|| node.label.contains("exists")
195196
|| node.label.contains("Pi")
197+
|| node.label.contains("Sigma")
196198
{
197199
1.0
198200
} else {
@@ -310,6 +312,8 @@ fn infer_term_kind_from_label(label: &str) -> usize {
310312
3 // Lambda
311313
} else if label.starts_with("pi_") {
312314
4 // Pi
315+
} else if label.starts_with("sigma_") {
316+
13 // Sigma
313317
} else if label.starts_with("let_") {
314318
7 // Let
315319
} else if label.starts_with("fix_") {

src/rust/gnn/graph.rs

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
use serde::{Deserialize, Serialize};
1818
use std::collections::HashMap;
1919

20-
use crate::core::{Context, Goal, Hypothesis, ProofState, Term, Theorem};
20+
use crate::core::{Context, Definition, Goal, Hypothesis, ProofState, Term, Theorem};
2121

2222
/// Kind of node in the proof graph.
2323
///
@@ -63,6 +63,12 @@ pub enum EdgeKind {
6363
DependsOn,
6464
/// Reverse of DependsOn (premise is useful for goal)
6565
UsefulFor,
66+
/// Source has the target as its QTT multiplicity annotation
67+
HasMultiplicity,
68+
/// Source has the target as its algebraic effect annotation
69+
HasEffect,
70+
/// Source has the target as its modal decoration
71+
HasModality,
6672
}
6773

6874
/// A single node in the proof graph.
@@ -185,18 +191,25 @@ impl ProofGraphBuilder {
185191
// Phase 2: Add hypothesis nodes from context
186192
let hyp_ids = self.add_hypotheses_from_context(&state.context);
187193

194+
// Phase 2b: Add definition nodes from context
195+
let def_ids = self.add_definitions_from_context(&state.context);
196+
188197
// Phase 3: Add premise/theorem nodes from context
189198
for theorem in &state.context.theorems {
190199
let pid = self.add_premise(theorem);
191200
premise_node_ids.push(pid);
192201
}
193202

194-
// Phase 4: Add dependency edges from goals to hypotheses/premises
203+
// Phase 4: Add dependency edges from goals to hypotheses/definitions/premises
195204
if let Some(gid) = goal_node_id {
196205
for &hid in &hyp_ids {
197206
self.add_edge(gid, hid, EdgeKind::DependsOn, 1.0);
198207
self.add_edge(hid, gid, EdgeKind::UsefulFor, 1.0);
199208
}
209+
for &did in &def_ids {
210+
self.add_edge(gid, did, EdgeKind::DependsOn, 0.9);
211+
self.add_edge(did, gid, EdgeKind::UsefulFor, 0.9);
212+
}
200213
for &pid in &premise_node_ids {
201214
self.add_edge(gid, pid, EdgeKind::DependsOn, 0.8);
202215
self.add_edge(pid, gid, EdgeKind::UsefulFor, 0.8);
@@ -278,6 +291,11 @@ impl ProofGraphBuilder {
278291
self.expand_term(body, hid, 1);
279292
}
280293

294+
// Wire type_info annotations as edges
295+
if let Some(ref info) = hyp.type_info {
296+
self.add_type_info_edges(hid, info);
297+
}
298+
281299
hid
282300
}
283301

@@ -288,11 +306,49 @@ impl ProofGraphBuilder {
288306
let label = format!("{}: {}", var.name, var.ty);
289307
let vid = self.add_node(NodeKind::Hypothesis, &label, 0);
290308
self.expand_term(&var.ty, vid, 1);
309+
310+
// Wire type_info annotations as edges
311+
if let Some(ref info) = var.type_info {
312+
self.add_type_info_edges(vid, info);
313+
}
314+
291315
ids.push(vid);
292316
}
293317
ids
294318
}
295319

320+
/// Add definition nodes from the global context.
321+
fn add_definitions_from_context(&mut self, ctx: &Context) -> Vec<usize> {
322+
let mut ids = Vec::new();
323+
for def in &ctx.definitions {
324+
let did = self.add_definition(def);
325+
ids.push(did);
326+
}
327+
ids
328+
}
329+
330+
/// Add a definition node.
331+
fn add_definition(&mut self, def: &Definition) -> usize {
332+
let label = format!("{}: {}", def.name, def.ty);
333+
let did = self.add_node(NodeKind::Hypothesis, &label, 0);
334+
335+
// Expand the definition type
336+
let type_id = self.expand_term(&def.ty, did, 1);
337+
if let Some(tid) = type_id {
338+
self.add_edge(did, tid, EdgeKind::HasType, 1.0);
339+
}
340+
341+
// Expand the definition body
342+
self.expand_term(&def.body, did, 1);
343+
344+
// Wire type_info annotations as edges
345+
if let Some(ref info) = def.type_info {
346+
self.add_type_info_edges(did, info);
347+
}
348+
349+
did
350+
}
351+
296352
/// Add a premise (theorem/lemma) node.
297353
fn add_premise(&mut self, theorem: &Theorem) -> usize {
298354
let label = format!("{}: {}", theorem.name, theorem.statement);
@@ -456,6 +512,32 @@ impl ProofGraphBuilder {
456512
}
457513
}
458514

515+
/// Add edges from a node to represent its [`TypeInfo`] annotations.
516+
///
517+
/// Creates labelled subterm nodes for multiplicity, effect, and modality
518+
/// decorations and connects them with the appropriate edge kind.
519+
fn add_type_info_edges(&mut self, node_id: usize, info: &crate::types::TypeInfo) {
520+
if let Some(ref mult) = info.multiplicity {
521+
let label = format!("mult:{:?}", mult);
522+
let mid = self.add_node(NodeKind::Subterm, &label, 1);
523+
self.add_edge(node_id, mid, EdgeKind::HasMultiplicity, 1.0);
524+
}
525+
526+
if !info.effects.is_empty() {
527+
for eff in &info.effects.effects {
528+
let label = format!("effect:{:?}", eff);
529+
let eid = self.add_node(NodeKind::Subterm, &label, 1);
530+
self.add_edge(node_id, eid, EdgeKind::HasEffect, 1.0);
531+
}
532+
}
533+
534+
if let Some(ref modality) = info.modality {
535+
let label = format!("modality:{:?}", modality);
536+
let mid = self.add_node(NodeKind::Subterm, &label, 1);
537+
self.add_edge(node_id, mid, EdgeKind::HasModality, 1.0);
538+
}
539+
}
540+
459541
/// Add shared-structure edges between premise nodes that share constants.
460542
///
461543
/// Two premises sharing named constants likely have semantic overlap,

src/rust/provers/fstar.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use tokio::process::Command;
1717

1818
use super::{ProverBackend, ProverConfig, ProverKind};
1919
use crate::core::{Goal, ProofState, Tactic, TacticResult, Term};
20+
use crate::types::{Effect, TypeInfo};
2021

2122
/// F* backend
2223
pub struct FStarBackend {
@@ -33,12 +34,51 @@ impl FStarBackend {
3334
for (i, axiom) in state.context.axioms.iter().enumerate() {
3435
input.push_str(&format!("assume val axiom_{} : {}\n", i, axiom));
3536
}
37+
// Emit definitions with effect and refinement annotations from TypeInfo
38+
for def in &state.context.definitions {
39+
let effect_str = def
40+
.type_info
41+
.as_ref()
42+
.map(Self::effect_to_fstar)
43+
.unwrap_or_default();
44+
let refinement_str = def
45+
.type_info
46+
.as_ref()
47+
.and_then(|ti| ti.refinement.as_ref())
48+
.map(|pred| format!("{{v:{} | {}}}", def.ty, pred))
49+
.unwrap_or_else(|| format!("{}", def.ty));
50+
input.push_str(&format!("val {} : {}{}\n", def.name, effect_str, refinement_str));
51+
}
3652
if let Some(goal) = state.goals.first() {
3753
input.push_str(&format!("\nval goal : {}\n", goal.target));
3854
}
3955
Ok(input)
4056
}
4157

58+
/// Convert effect row from [`TypeInfo`] to an F* effect annotation prefix.
59+
fn effect_to_fstar(ti: &TypeInfo) -> String {
60+
if ti.effects.is_empty() {
61+
return String::new();
62+
}
63+
let effect_name = ti
64+
.effects
65+
.effects
66+
.first()
67+
.map(|e| match e {
68+
Effect::Pure | Effect::Tot => "Tot",
69+
Effect::IO => "IO",
70+
Effect::State => "ST",
71+
Effect::Exception => "Exn",
72+
Effect::Div => "Div",
73+
Effect::Ghost => "GTot",
74+
Effect::NonDet => "ALL",
75+
Effect::Async => "ALL",
76+
Effect::Custom(s) => s.as_str(),
77+
})
78+
.unwrap_or("Tot");
79+
format!("{} ", effect_name)
80+
}
81+
4282
fn parse_result(&self, output: &str) -> Result<bool> {
4383
let lower = output.to_lowercase();
4484
if lower.contains("verified") || lower.contains("all verification conditions discharged") {

src/rust/provers/idris2.rs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use crate::core::{
3030
Theorem,
3131
};
3232
use crate::provers::{ProverBackend, ProverConfig, ProverKind};
33+
use crate::types::Multiplicity;
3334

3435
/// Idris 2 backend implementation
3536
pub struct Idris2Backend {
@@ -105,17 +106,6 @@ enum Idris2Term {
105106
AutoImplicit(String, Box<Idris2Term>),
106107
}
107108

108-
/// Quantitative type theory multiplicities
109-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110-
enum Multiplicity {
111-
/// Unrestricted (0, 1, or many uses)
112-
Unrestricted,
113-
/// Linear (exactly 1 use)
114-
Linear,
115-
/// Erased (0 uses at runtime)
116-
Erased,
117-
}
118-
119109
impl Idris2Backend {
120110
pub fn new(config: ProverConfig) -> Self {
121111
Idris2Backend {
@@ -178,6 +168,17 @@ impl Idris2Backend {
178168
}
179169
}
180170

171+
/// Convert a [`Multiplicity`] to the Idris 2 QTT annotation string.
172+
fn multiplicity_to_idris2(m: &Multiplicity) -> &'static str {
173+
match m {
174+
Multiplicity::Zero => "0",
175+
Multiplicity::One | Multiplicity::Linear => "1",
176+
Multiplicity::Omega | Multiplicity::Shared => "",
177+
Multiplicity::Affine => "1", // closest QTT approximation
178+
Multiplicity::Graded(_) => "",
179+
}
180+
}
181+
181182
/// Convert universal Term to Idris 2 syntax
182183
fn term_to_idris2(&self, term: &Term) -> String {
183184
match term {
@@ -815,11 +816,17 @@ impl ProverBackend for Idris2Backend {
815816
output.push_str("import Data.Vect\n");
816817
output.push_str("import Data.Nat\n\n");
817818

818-
// Definitions
819+
// Definitions (emit QTT multiplicity annotations when present)
819820
for def in &state.context.definitions {
820821
let ty_str = self.term_to_idris2(&def.ty);
821822
let body_str = self.term_to_idris2(&def.body);
822-
output.push_str(&format!("{} : {}\n", def.name, ty_str));
823+
let mult_prefix = def
824+
.type_info
825+
.as_ref()
826+
.and_then(|ti| ti.multiplicity.as_ref())
827+
.map(|m| format!("{} ", Self::multiplicity_to_idris2(m)))
828+
.unwrap_or_default();
829+
output.push_str(&format!("{}{} : {}\n", mult_prefix, def.name, ty_str));
823830
output.push_str(&format!("{} = {}\n\n", def.name, body_str));
824831
}
825832

@@ -1210,7 +1217,7 @@ mod tests {
12101217
// Test Pi type
12111218
let pi_term = Idris2Term::Pi(
12121219
"a".to_string(),
1213-
Multiplicity::Unrestricted,
1220+
Multiplicity::Omega,
12141221
Box::new(Idris2Term::Type),
12151222
Box::new(Idris2Term::Var("a".to_string())),
12161223
);

src/rust/provers/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ pub mod mizar;
4545
pub mod nuprl;
4646
pub mod nusmv;
4747
pub mod ortools;
48+
pub mod outcome;
4849
pub mod prism;
4950
pub mod proverif;
5051
pub mod pvs;

0 commit comments

Comments
 (0)