Skip to main content

oxilean_std/program_extraction/
functions.rs

1//! Functions for program extraction from constructive proofs via the Curry-Howard correspondence.
2
3use super::types::{ConstructiveProp, ExtractedProgram, ExtractionResult, ProofTerm};
4
5// ── Core extraction ───────────────────────────────────────────────────────────
6
7/// Attempt to extract a computational program from a constructive proof.
8///
9/// The Curry-Howard isomorphism maps:
10/// - propositions ↔ types
11/// - proofs       ↔ programs
12///
13/// Returns `ExtractionResult::Success` when the proof is well-typed and the
14/// proposition is fully constructive.
15pub fn extract_program(prop: &ConstructiveProp, proof: &ProofTerm) -> ExtractionResult {
16    if !is_constructive(prop) {
17        return ExtractionResult::NonConstructive(format!(
18            "Proposition contains non-constructive connectives: {:?}",
19            prop
20        ));
21    }
22    if !type_check_proof(prop, proof) {
23        return ExtractionResult::Failure(format!(
24            "Proof term does not type-check against proposition {:?}",
25            prop
26        ));
27    }
28    let (input_type, output_type) = infer_io_types(prop);
29    let simplified = simplify_proof_term(proof);
30    ExtractionResult::Success(ExtractedProgram {
31        name: format!("extracted_{}", prop_name(prop)),
32        input_type,
33        output_type,
34        body: simplified,
35        original_prop: prop.clone(),
36    })
37}
38
39/// Perform beta/eta reduction on a proof term, returning a simplified equivalent.
40pub fn simplify_proof_term(pt: &ProofTerm) -> ProofTerm {
41    match pt {
42        // Beta: (λx.b) a → b[x ↦ a]
43        ProofTerm::App(f, arg) => {
44            let f2 = simplify_proof_term(f);
45            let a2 = simplify_proof_term(arg);
46            if let ProofTerm::Lambda(var, body) = f2 {
47                let substituted = subst_proof_term(&body, &var, &a2);
48                simplify_proof_term(&substituted)
49            } else {
50                ProofTerm::App(Box::new(f2), Box::new(a2))
51            }
52        }
53        // Eta on Fst/Snd applied to Pair: fst(pair(a,b)) → a
54        ProofTerm::Fst(inner) => {
55            let inner2 = simplify_proof_term(inner);
56            if let ProofTerm::Pair(a, _) = inner2 {
57                simplify_proof_term(&a)
58            } else {
59                ProofTerm::Fst(Box::new(inner2))
60            }
61        }
62        ProofTerm::Snd(inner) => {
63            let inner2 = simplify_proof_term(inner);
64            if let ProofTerm::Pair(_, b) = inner2 {
65                simplify_proof_term(&b)
66            } else {
67                ProofTerm::Snd(Box::new(inner2))
68            }
69        }
70        ProofTerm::Lambda(x, body) => {
71            ProofTerm::Lambda(x.clone(), Box::new(simplify_proof_term(body)))
72        }
73        ProofTerm::Pair(a, b) => ProofTerm::Pair(
74            Box::new(simplify_proof_term(a)),
75            Box::new(simplify_proof_term(b)),
76        ),
77        ProofTerm::Inl(t) => ProofTerm::Inl(Box::new(simplify_proof_term(t))),
78        ProofTerm::Inr(t) => ProofTerm::Inr(Box::new(simplify_proof_term(t))),
79        ProofTerm::Pack(w, t) => ProofTerm::Pack(w.clone(), Box::new(simplify_proof_term(t))),
80        ProofTerm::Unpack {
81            witness,
82            proof_var,
83            packed,
84            body,
85        } => ProofTerm::Unpack {
86            witness: witness.clone(),
87            proof_var: proof_var.clone(),
88            packed: Box::new(simplify_proof_term(packed)),
89            body: Box::new(simplify_proof_term(body)),
90        },
91        ProofTerm::Absurd(t) => ProofTerm::Absurd(Box::new(simplify_proof_term(t))),
92        ProofTerm::Unit | ProofTerm::Var(_) => pt.clone(),
93    }
94}
95
96/// Verify that a proof term is well-typed with respect to a proposition.
97///
98/// Implements the type-checking rules of the Curry-Howard correspondence:
99/// - `Unit` proves `True`
100/// - `Pair(a, b)` proves `And(P, Q)` when `a` proves `P` and `b` proves `Q`
101/// - `Lambda(x, body)` proves `Implies(P, Q)` etc.
102pub fn type_check_proof(prop: &ConstructiveProp, proof: &ProofTerm) -> bool {
103    match (prop, proof) {
104        // ⊤ is proved by unit
105        (ConstructiveProp::True, ProofTerm::Unit) => true,
106        // ⊥ has no proof (but Absurd is typed at any conclusion)
107        (_, ProofTerm::Absurd(_)) => true,
108        // Var can prove anything (open term / axiom)
109        (_, ProofTerm::Var(_)) => true,
110        // Conjunction: pair witnesses both parts
111        (ConstructiveProp::And(p, q), ProofTerm::Pair(a, b)) => {
112            type_check_proof(p, a) && type_check_proof(q, b)
113        }
114        // Fst projects left
115        (p, ProofTerm::Fst(inner)) => {
116            // The inner must prove And(p, _)
117            matches_and_left(p, inner)
118        }
119        // Snd projects right
120        (q, ProofTerm::Snd(inner)) => matches_and_right(q, inner),
121        // Disjunction left injection
122        (ConstructiveProp::Or(p, _), ProofTerm::Inl(t)) => type_check_proof(p, t),
123        // Disjunction right injection
124        (ConstructiveProp::Or(_, q), ProofTerm::Inr(t)) => type_check_proof(q, t),
125        // Implication: lambda abstraction
126        (ConstructiveProp::Implies(_, q), ProofTerm::Lambda(_, body)) => {
127            // We can only check the codomain without a context; approximate
128            type_check_proof(q, body)
129        }
130        // Negation: ¬P = P → ⊥, lambda proves implication
131        (ConstructiveProp::Not(_), ProofTerm::Lambda(_, _)) => true,
132        // Existential: pack(w, pf) proves ∃x.P(x)
133        (ConstructiveProp::Exists(_, _), ProofTerm::Pack(_, _)) => true,
134        // Universal: lambda proves ∀
135        (ConstructiveProp::Forall(_, _), ProofTerm::Lambda(_, _)) => true,
136        // Unpack of an existential
137        (_, ProofTerm::Unpack { .. }) => true,
138        // Application: modus ponens — hard to check without full context
139        (_, ProofTerm::App(_, _)) => true,
140        // Equality: unit witnesses reflexivity
141        (ConstructiveProp::Eq(a, b), ProofTerm::Unit) => a == b,
142        // Atom: unit witnesses trivial atoms
143        (ConstructiveProp::Atom(_, _), ProofTerm::Unit) => true,
144        _ => false,
145    }
146}
147
148/// Check whether a proposition is fully constructive (intuitionistically valid).
149///
150/// A proposition is constructive if it avoids the law of excluded middle and
151/// double-negation elimination at top level, and all subpropositions are also
152/// constructive.
153pub fn is_constructive(prop: &ConstructiveProp) -> bool {
154    match prop {
155        ConstructiveProp::True | ConstructiveProp::False => true,
156        ConstructiveProp::Atom(_, _) | ConstructiveProp::Eq(_, _) => true,
157        ConstructiveProp::And(p, q)
158        | ConstructiveProp::Or(p, q)
159        | ConstructiveProp::Implies(p, q) => is_constructive(p) && is_constructive(q),
160        ConstructiveProp::Not(p) => {
161            // ¬P = P → ⊥ is constructive as long as P is constructive
162            is_constructive(p)
163        }
164        ConstructiveProp::Exists(_, p) | ConstructiveProp::Forall(_, p) => is_constructive(p),
165    }
166}
167
168/// Check whether a proposition has a realizer — i.e., there exists a computable
169/// function that extracts a witness from any proof of the proposition.
170///
171/// This is a syntactic approximation of realizability: propositions built
172/// entirely from ⊤, ∧, ∨, →, ∃ (with concrete witnesses), and atoms are
173/// realizable. ⊥ and ¬ are only realizable in trivial ways.
174pub fn realizability_check(prop: &ConstructiveProp) -> bool {
175    match prop {
176        ConstructiveProp::True => true,
177        ConstructiveProp::False => false,
178        ConstructiveProp::Atom(_, _) => true,
179        ConstructiveProp::Eq(a, b) => a == b,
180        ConstructiveProp::And(p, q) => realizability_check(p) && realizability_check(q),
181        ConstructiveProp::Or(p, q) => realizability_check(p) || realizability_check(q),
182        ConstructiveProp::Implies(p, q) => {
183            // P → Q is realizable if Q is realizable (or P is not)
184            !realizability_check(p) || realizability_check(q)
185        }
186        ConstructiveProp::Not(p) => !realizability_check(p),
187        ConstructiveProp::Exists(_, p) => realizability_check(p),
188        ConstructiveProp::Forall(_, p) => realizability_check(p),
189    }
190}
191
192/// Generate a Rust pseudocode string representing the extracted program.
193pub fn proof_to_rust(prog: &ExtractedProgram) -> String {
194    let body_str = proof_term_to_rust(&prog.body, 1);
195    format!(
196        "fn {}(input: {}) -> {} {{\n{}\n}}",
197        sanitize_ident(&prog.name),
198        prog.input_type,
199        prog.output_type,
200        body_str,
201    )
202}
203
204/// Extract a conclusion from modus ponens:
205/// given a proof of P and a proof of P → Q, produce a proof of Q.
206pub fn modus_ponens_extract(
207    _p: &ConstructiveProp,
208    _q: &ConstructiveProp,
209    pf_p: &ProofTerm,
210    pf_pq: &ProofTerm,
211) -> ProofTerm {
212    ProofTerm::App(Box::new(pf_pq.clone()), Box::new(pf_p.clone()))
213}
214
215/// Construct a proof of P ∧ Q from individual proofs.
216pub fn conjunction_intro(pf_p: &ProofTerm, pf_q: &ProofTerm) -> ProofTerm {
217    ProofTerm::Pair(Box::new(pf_p.clone()), Box::new(pf_q.clone()))
218}
219
220/// Pack a witness value with a proof to produce a proof of ∃x.P(x).
221pub fn existential_witness(witness: &str, pf: &ProofTerm) -> ProofTerm {
222    ProofTerm::Pack(witness.to_string(), Box::new(pf.clone()))
223}
224
225// ── Internal helpers ──────────────────────────────────────────────────────────
226
227/// Substitute `var` with `val` in a proof term (capture-avoiding, simplified).
228pub(super) fn subst_proof_term(term: &ProofTerm, var: &str, val: &ProofTerm) -> ProofTerm {
229    match term {
230        ProofTerm::Var(x) if x == var => val.clone(),
231        ProofTerm::Var(_) | ProofTerm::Unit => term.clone(),
232        ProofTerm::Lambda(x, body) => {
233            if x == var {
234                // Variable is shadowed; do not substitute inside
235                term.clone()
236            } else {
237                ProofTerm::Lambda(x.clone(), Box::new(subst_proof_term(body, var, val)))
238            }
239        }
240        ProofTerm::App(f, a) => ProofTerm::App(
241            Box::new(subst_proof_term(f, var, val)),
242            Box::new(subst_proof_term(a, var, val)),
243        ),
244        ProofTerm::Pair(a, b) => ProofTerm::Pair(
245            Box::new(subst_proof_term(a, var, val)),
246            Box::new(subst_proof_term(b, var, val)),
247        ),
248        ProofTerm::Fst(t) => ProofTerm::Fst(Box::new(subst_proof_term(t, var, val))),
249        ProofTerm::Snd(t) => ProofTerm::Snd(Box::new(subst_proof_term(t, var, val))),
250        ProofTerm::Inl(t) => ProofTerm::Inl(Box::new(subst_proof_term(t, var, val))),
251        ProofTerm::Inr(t) => ProofTerm::Inr(Box::new(subst_proof_term(t, var, val))),
252        ProofTerm::Pack(w, t) => {
253            ProofTerm::Pack(w.clone(), Box::new(subst_proof_term(t, var, val)))
254        }
255        ProofTerm::Unpack {
256            witness,
257            proof_var,
258            packed,
259            body,
260        } => {
261            let packed2 = subst_proof_term(packed, var, val);
262            let body2 = if witness == var || proof_var == var {
263                // Shadowed
264                *body.clone()
265            } else {
266                subst_proof_term(body, var, val)
267            };
268            ProofTerm::Unpack {
269                witness: witness.clone(),
270                proof_var: proof_var.clone(),
271                packed: Box::new(packed2),
272                body: Box::new(body2),
273            }
274        }
275        ProofTerm::Absurd(t) => ProofTerm::Absurd(Box::new(subst_proof_term(t, var, val))),
276    }
277}
278
279/// Derive a short descriptive name from a proposition.
280fn prop_name(prop: &ConstructiveProp) -> String {
281    match prop {
282        ConstructiveProp::True => "true".to_string(),
283        ConstructiveProp::False => "false".to_string(),
284        ConstructiveProp::And(_, _) => "and".to_string(),
285        ConstructiveProp::Or(_, _) => "or".to_string(),
286        ConstructiveProp::Not(_) => "not".to_string(),
287        ConstructiveProp::Implies(_, _) => "implies".to_string(),
288        ConstructiveProp::Exists(x, _) => format!("exists_{}", x),
289        ConstructiveProp::Forall(x, _) => format!("forall_{}", x),
290        ConstructiveProp::Atom(name, _) => name.clone(),
291        ConstructiveProp::Eq(a, b) => format!("eq_{}_{}", a, b),
292    }
293}
294
295/// Infer the (input_type, output_type) strings for an extracted program.
296fn infer_io_types(prop: &ConstructiveProp) -> (String, String) {
297    match prop {
298        ConstructiveProp::Implies(p, q) => (prop_type_str(p), prop_type_str(q)),
299        ConstructiveProp::Forall(x, p) => (x.clone(), prop_type_str(p)),
300        _ => ("()".to_string(), prop_type_str(prop)),
301    }
302}
303
304/// Convert a proposition to a Rust type string (for code generation).
305fn prop_type_str(prop: &ConstructiveProp) -> String {
306    match prop {
307        ConstructiveProp::True => "()".to_string(),
308        ConstructiveProp::False => "!".to_string(),
309        ConstructiveProp::And(p, q) => format!("({}, {})", prop_type_str(p), prop_type_str(q)),
310        ConstructiveProp::Or(p, q) => format!("Result<{}, {}>", prop_type_str(p), prop_type_str(q)),
311        ConstructiveProp::Not(p) => format!("fn({}) -> !", prop_type_str(p)),
312        ConstructiveProp::Implies(p, q) => {
313            format!("fn({}) -> {}", prop_type_str(p), prop_type_str(q))
314        }
315        ConstructiveProp::Exists(x, _) => format!("({}, _)", x),
316        ConstructiveProp::Forall(_, p) => format!("impl Fn(_) -> {}", prop_type_str(p)),
317        ConstructiveProp::Atom(name, args) => {
318            if args.is_empty() {
319                name.clone()
320            } else {
321                format!("{}<{}>", name, args.join(", "))
322            }
323        }
324        ConstructiveProp::Eq(a, b) => format!("Eq<{}, {}>", a, b),
325    }
326}
327
328/// Convert a proof term to a Rust code string (recursive, with indentation).
329fn proof_term_to_rust(pt: &ProofTerm, depth: usize) -> String {
330    let indent = "    ".repeat(depth);
331    match pt {
332        ProofTerm::Unit => format!("{}()", indent),
333        ProofTerm::Var(x) => format!("{}{}", indent, x),
334        ProofTerm::Lambda(x, body) => {
335            let body_str = proof_term_to_rust(body, depth + 1);
336            format!("{}|{}| {{\n{}\n{}}}", indent, x, body_str, indent)
337        }
338        ProofTerm::App(f, a) => {
339            let f_str = proof_term_to_rust(f, 0);
340            let a_str = proof_term_to_rust(a, 0);
341            format!("{}({}).({})", indent, f_str.trim(), a_str.trim())
342        }
343        ProofTerm::Pair(a, b) => {
344            let a_str = proof_term_to_rust(a, 0).trim().to_string();
345            let b_str = proof_term_to_rust(b, 0).trim().to_string();
346            format!("{}({}, {})", indent, a_str, b_str)
347        }
348        ProofTerm::Fst(t) => {
349            let inner = proof_term_to_rust(t, 0).trim().to_string();
350            format!("{}{}.0", indent, inner)
351        }
352        ProofTerm::Snd(t) => {
353            let inner = proof_term_to_rust(t, 0).trim().to_string();
354            format!("{}{}.1", indent, inner)
355        }
356        ProofTerm::Inl(t) => {
357            let inner = proof_term_to_rust(t, 0).trim().to_string();
358            format!("{}Ok({})", indent, inner)
359        }
360        ProofTerm::Inr(t) => {
361            let inner = proof_term_to_rust(t, 0).trim().to_string();
362            format!("{}Err({})", indent, inner)
363        }
364        ProofTerm::Pack(w, t) => {
365            let inner = proof_term_to_rust(t, 0).trim().to_string();
366            format!("{}({}, {})", indent, w, inner)
367        }
368        ProofTerm::Unpack {
369            witness,
370            proof_var,
371            packed,
372            body,
373        } => {
374            let packed_str = proof_term_to_rust(packed, 0).trim().to_string();
375            let body_str = proof_term_to_rust(body, depth + 1);
376            format!(
377                "{}let ({}, {}) = {};\n{}",
378                indent, witness, proof_var, packed_str, body_str
379            )
380        }
381        ProofTerm::Absurd(t) => {
382            let inner = proof_term_to_rust(t, 0).trim().to_string();
383            format!("{}{{ let _: ! = {}; unreachable!() }}", indent, inner)
384        }
385    }
386}
387
388/// Check if `inner` proves `And(p, _)` and thus `Fst(inner)` proves `p`.
389fn matches_and_left(p: &ConstructiveProp, inner: &ProofTerm) -> bool {
390    // Without a full context we can only do a syntactic approximation
391    matches!(inner, ProofTerm::Pair(a, _) if type_check_proof(p, a))
392        || matches!(inner, ProofTerm::Var(_))
393}
394
395/// Check if `inner` proves `And(_, q)` and thus `Snd(inner)` proves `q`.
396fn matches_and_right(q: &ConstructiveProp, inner: &ProofTerm) -> bool {
397    matches!(inner, ProofTerm::Pair(_, b) if type_check_proof(q, b))
398        || matches!(inner, ProofTerm::Var(_))
399}
400
401/// Sanitize a string to be a valid Rust identifier.
402fn sanitize_ident(s: &str) -> String {
403    s.chars()
404        .map(|c| {
405            if c.is_alphanumeric() || c == '_' {
406                c
407            } else {
408                '_'
409            }
410        })
411        .collect()
412}
413
414// ── Tests ─────────────────────────────────────────────────────────────────────
415
416#[cfg(test)]
417mod tests {
418    use super::super::types::*;
419    use super::*;
420
421    fn atom(name: &str) -> ConstructiveProp {
422        ConstructiveProp::Atom(name.to_string(), vec![])
423    }
424
425    fn var(x: &str) -> ProofTerm {
426        ProofTerm::Var(x.to_string())
427    }
428
429    // ── is_constructive ───────────────────────────────────────────────────────
430
431    #[test]
432    fn test_is_constructive_true() {
433        assert!(is_constructive(&ConstructiveProp::True));
434    }
435
436    #[test]
437    fn test_is_constructive_false() {
438        assert!(is_constructive(&ConstructiveProp::False));
439    }
440
441    #[test]
442    fn test_is_constructive_atom() {
443        assert!(is_constructive(&atom("P")));
444    }
445
446    #[test]
447    fn test_is_constructive_and() {
448        let p = ConstructiveProp::And(Box::new(atom("P")), Box::new(atom("Q")));
449        assert!(is_constructive(&p));
450    }
451
452    #[test]
453    fn test_is_constructive_or() {
454        let p = ConstructiveProp::Or(Box::new(atom("P")), Box::new(atom("Q")));
455        assert!(is_constructive(&p));
456    }
457
458    #[test]
459    fn test_is_constructive_implies() {
460        let p = ConstructiveProp::Implies(Box::new(atom("P")), Box::new(atom("Q")));
461        assert!(is_constructive(&p));
462    }
463
464    #[test]
465    fn test_is_constructive_not() {
466        let p = ConstructiveProp::Not(Box::new(atom("P")));
467        assert!(is_constructive(&p));
468    }
469
470    #[test]
471    fn test_is_constructive_exists() {
472        let p = ConstructiveProp::Exists("x".to_string(), Box::new(atom("P")));
473        assert!(is_constructive(&p));
474    }
475
476    #[test]
477    fn test_is_constructive_forall() {
478        let p = ConstructiveProp::Forall("x".to_string(), Box::new(atom("P")));
479        assert!(is_constructive(&p));
480    }
481
482    // ── type_check_proof ──────────────────────────────────────────────────────
483
484    #[test]
485    fn test_type_check_unit_proves_true() {
486        assert!(type_check_proof(&ConstructiveProp::True, &ProofTerm::Unit));
487    }
488
489    #[test]
490    fn test_type_check_pair_proves_and() {
491        let prop = ConstructiveProp::And(Box::new(atom("P")), Box::new(atom("Q")));
492        let pf = ProofTerm::Pair(Box::new(var("p")), Box::new(var("q")));
493        assert!(type_check_proof(&prop, &pf));
494    }
495
496    #[test]
497    fn test_type_check_inl_proves_or_left() {
498        let prop = ConstructiveProp::Or(Box::new(atom("P")), Box::new(atom("Q")));
499        let pf = ProofTerm::Inl(Box::new(var("p")));
500        assert!(type_check_proof(&prop, &pf));
501    }
502
503    #[test]
504    fn test_type_check_inr_proves_or_right() {
505        let prop = ConstructiveProp::Or(Box::new(atom("P")), Box::new(atom("Q")));
506        let pf = ProofTerm::Inr(Box::new(var("q")));
507        assert!(type_check_proof(&prop, &pf));
508    }
509
510    #[test]
511    fn test_type_check_lambda_proves_implies() {
512        let prop = ConstructiveProp::Implies(Box::new(atom("P")), Box::new(atom("Q")));
513        let pf = ProofTerm::Lambda("x".to_string(), Box::new(var("x")));
514        assert!(type_check_proof(&prop, &pf));
515    }
516
517    #[test]
518    fn test_type_check_absurd_proves_anything() {
519        let pf = ProofTerm::Absurd(Box::new(var("contradiction")));
520        assert!(type_check_proof(&atom("Q"), &pf));
521    }
522
523    #[test]
524    fn test_type_check_var_proves_anything() {
525        assert!(type_check_proof(&atom("P"), &var("p")));
526    }
527
528    // ── realizability_check ───────────────────────────────────────────────────
529
530    #[test]
531    fn test_realizability_true() {
532        assert!(realizability_check(&ConstructiveProp::True));
533    }
534
535    #[test]
536    fn test_realizability_false() {
537        assert!(!realizability_check(&ConstructiveProp::False));
538    }
539
540    #[test]
541    fn test_realizability_atom() {
542        assert!(realizability_check(&atom("P")));
543    }
544
545    #[test]
546    fn test_realizability_and_true_true() {
547        let p = ConstructiveProp::And(
548            Box::new(ConstructiveProp::True),
549            Box::new(ConstructiveProp::True),
550        );
551        assert!(realizability_check(&p));
552    }
553
554    #[test]
555    fn test_realizability_and_with_false() {
556        let p = ConstructiveProp::And(
557            Box::new(ConstructiveProp::True),
558            Box::new(ConstructiveProp::False),
559        );
560        assert!(!realizability_check(&p));
561    }
562
563    #[test]
564    fn test_realizability_or_true_false() {
565        let p = ConstructiveProp::Or(
566            Box::new(ConstructiveProp::True),
567            Box::new(ConstructiveProp::False),
568        );
569        assert!(realizability_check(&p));
570    }
571
572    // ── simplify_proof_term ───────────────────────────────────────────────────
573
574    #[test]
575    fn test_simplify_beta_redex() {
576        // (λx.x) y → y
577        let lam = ProofTerm::Lambda("x".to_string(), Box::new(var("x")));
578        let app = ProofTerm::App(Box::new(lam), Box::new(var("y")));
579        assert_eq!(simplify_proof_term(&app), var("y"));
580    }
581
582    #[test]
583    fn test_simplify_fst_pair() {
584        let pair = ProofTerm::Pair(Box::new(var("a")), Box::new(var("b")));
585        let fst = ProofTerm::Fst(Box::new(pair));
586        assert_eq!(simplify_proof_term(&fst), var("a"));
587    }
588
589    #[test]
590    fn test_simplify_snd_pair() {
591        let pair = ProofTerm::Pair(Box::new(var("a")), Box::new(var("b")));
592        let snd = ProofTerm::Snd(Box::new(pair));
593        assert_eq!(simplify_proof_term(&snd), var("b"));
594    }
595
596    #[test]
597    fn test_simplify_unit() {
598        assert_eq!(simplify_proof_term(&ProofTerm::Unit), ProofTerm::Unit);
599    }
600
601    // ── extract_program ───────────────────────────────────────────────────────
602
603    #[test]
604    fn test_extract_simple_implication() {
605        let prop = ConstructiveProp::Implies(Box::new(atom("P")), Box::new(atom("P")));
606        let pf = ProofTerm::Lambda("x".to_string(), Box::new(var("x")));
607        let result = extract_program(&prop, &pf);
608        assert!(matches!(result, ExtractionResult::Success(_)));
609    }
610
611    #[test]
612    fn test_extract_conjunction_intro() {
613        let prop = ConstructiveProp::And(Box::new(atom("P")), Box::new(atom("Q")));
614        let pf = ProofTerm::Pair(Box::new(var("p")), Box::new(var("q")));
615        let result = extract_program(&prop, &pf);
616        assert!(matches!(result, ExtractionResult::Success(_)));
617    }
618
619    // ── modus_ponens_extract ──────────────────────────────────────────────────
620
621    #[test]
622    fn test_modus_ponens_extract() {
623        let p = atom("P");
624        let q = atom("Q");
625        let pf_p = var("p");
626        let pf_pq = ProofTerm::Lambda("x".to_string(), Box::new(var("x")));
627        let result = modus_ponens_extract(&p, &q, &pf_p, &pf_pq);
628        assert_eq!(result, ProofTerm::App(Box::new(pf_pq), Box::new(pf_p)));
629    }
630
631    // ── conjunction_intro / existential_witness ───────────────────────────────
632
633    #[test]
634    fn test_conjunction_intro() {
635        let pf_p = var("p");
636        let pf_q = var("q");
637        let result = conjunction_intro(&pf_p, &pf_q);
638        assert_eq!(result, ProofTerm::Pair(Box::new(pf_p), Box::new(pf_q)));
639    }
640
641    #[test]
642    fn test_existential_witness() {
643        let pf = var("body");
644        let result = existential_witness("42", &pf);
645        assert_eq!(result, ProofTerm::Pack("42".to_string(), Box::new(pf)));
646    }
647
648    // ── proof_to_rust ─────────────────────────────────────────────────────────
649
650    #[test]
651    fn test_proof_to_rust_identity() {
652        let prog = ExtractedProgram {
653            name: "identity".to_string(),
654            input_type: "P".to_string(),
655            output_type: "P".to_string(),
656            body: ProofTerm::Lambda("x".to_string(), Box::new(var("x"))),
657            original_prop: ConstructiveProp::Implies(Box::new(atom("P")), Box::new(atom("P"))),
658        };
659        let code = proof_to_rust(&prog);
660        assert!(code.contains("fn identity"));
661        assert!(code.contains("|x|"));
662    }
663
664    #[test]
665    fn test_proof_to_rust_pair() {
666        let prog = ExtractedProgram {
667            name: "pair_prog".to_string(),
668            input_type: "()".to_string(),
669            output_type: "(P, Q)".to_string(),
670            body: ProofTerm::Pair(Box::new(var("p")), Box::new(var("q"))),
671            original_prop: ConstructiveProp::And(Box::new(atom("P")), Box::new(atom("Q"))),
672        };
673        let code = proof_to_rust(&prog);
674        assert!(code.contains("fn pair_prog"));
675    }
676}