Skip to main content

logicaffeine_compile/extraction/
codegen.rs

1//! Rust code generation from kernel terms.
2//!
3//! Converts verified kernel terms to executable Rust source code while
4//! preserving the structural properties of the proof.
5//!
6//! # Code Generation Strategy
7//!
8//! | Kernel Term | Rust Output |
9//! |-------------|-------------|
10//! | Inductive | `enum Name { Ctor(Args...) }` |
11//! | Fix + Lambda | `fn name(args) -> Ret { body }` |
12//! | Lambda | `fn name(args) -> Ret { body }` |
13//! | Const | `const NAME: Ty = val;` |
14//! | Match | `match disc { Name::Ctor(vars) => body }` |
15//! | App | `func(arg)` |
16//!
17//! # Recursive Types
18//!
19//! Recursive inductive types (like `Nat`) have their recursive fields
20//! wrapped in `Box<T>` to ensure finite size:
21//!
22//! ```text
23//! Kernel:  Inductive Nat := Zero | Succ (Nat)
24//! Rust:    enum Nat { Zero, Succ(Box<Nat>) }
25//! ```
26
27use super::error::ExtractError;
28use crate::kernel::{Context, Literal, Term};
29use std::collections::{HashMap, HashSet};
30
31/// Context for code generation within a term.
32struct TermGenCtx<'a> {
33    /// The name of the definition being emitted
34    def_name: &'a str,
35    /// The name of the recursive reference (from Fix)
36    rec_name: &'a str,
37    /// Variables that need to be dereferenced (boxed in match patterns)
38    deref_vars: &'a HashSet<String>,
39    /// Variables referenced more than once in the body. The kernel's semantics
40    /// are non-linear (a value may be used any number of times), but Rust moves
41    /// by default, so each use of such a variable is emitted as `x.clone()`.
42    multi_use: &'a HashSet<String>,
43}
44
45/// Code generator for extracting Rust from kernel terms.
46pub struct CodeGen<'a> {
47    ctx: &'a Context,
48    output: String,
49    emitted: HashSet<String>,
50}
51
52impl<'a> CodeGen<'a> {
53    /// Create a new code generator.
54    pub fn new(ctx: &'a Context) -> Self {
55        Self {
56            ctx,
57            output: String::new(),
58            emitted: HashSet::new(),
59        }
60    }
61
62    /// Get the generated Rust code.
63    pub fn finish(self) -> String {
64        self.output
65    }
66
67    /// Emit an inductive type as a Rust enum.
68    pub fn emit_inductive(&mut self, name: &str) -> Result<(), ExtractError> {
69        if self.emitted.contains(name) {
70            return Ok(());
71        }
72        self.emitted.insert(name.to_string());
73
74        let ctors = distinct_constructors(self.ctx, name);
75        if ctors.is_empty() {
76            return Err(ExtractError::NotFound(name.to_string()));
77        }
78
79        // Type parameters of a polymorphic inductive (e.g. `A` in `MyList (A : Type)`)
80        // are encoded as leading Π-binders on the sort and on each constructor type;
81        // they become Rust generics, NOT data fields.
82        let params = type_params(self.ctx, name);
83        let generics = if params.is_empty() {
84            String::new()
85        } else {
86            format!("<{}>", params.join(", "))
87        };
88
89        // Clone: non-linear use needs `.clone()`. Debug/PartialEq: the
90        // self-verifying demo `main` prints values (`{:?}`) and `assert_eq!`s them
91        // against the kernel-computed result.
92        self.output.push_str("#[derive(Clone, Debug, PartialEq)]\n");
93        self.output.push_str(&format!("pub enum {}{} {{\n", name, generics));
94        for (ctor_name, ctor_ty) in &ctors {
95            let fields = extract_ctor_fields(self.ctx, ctor_ty, name, params.len());
96            if fields.is_empty() {
97                self.output.push_str(&format!("    {},\n", ctor_name));
98            } else {
99                self.output
100                    .push_str(&format!("    {}({}),\n", ctor_name, fields.join(", ")));
101            }
102        }
103        self.output.push_str("}\n\n");
104
105        // For a non-generic inductive, also emit a CONSTRUCTOR API + Display impl so
106        // IMPERATIVE LOGOS can build, pass, and `Show` proven values by name through
107        // `use proven::*;` (e.g. `MSucc(MSucc(MZero))`) with no codegen changes:
108        //   * nullary ctor  → `pub const Z: Nat = Nat::Z;`           (a value, not a fn)
109        //   * n-ary ctor    → `pub fn S(a0: Nat) -> Nat { Nat::S(Box::new(a0)) }`
110        //     (recursive fields boxed to match the tuple-variant shape)
111        //   * `impl Display` via Debug, since imperative `Show` formats with `{}`.
112        // Generic inductives are skipped (their values aren't constructible from the
113        // imperative side without type arguments).
114        if params.is_empty() {
115            self.output.push_str(&format!(
116                "impl std::fmt::Display for {n} {{\n    \
117                 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{ write!(f, \"{{:?}}\", self) }}\n}}\n\n",
118                n = name,
119            ));
120            for (ctor_name, ctor_ty) in &ctors {
121                // A ctor named after a std prelude value (`Some`/`None`/`Ok`/`Err`)
122                // gets NO free wrapper: under `use proven::*;` it would silently shadow
123                // the prelude in the imperative half. The qualified variant still works.
124                if matches!(*ctor_name, "Some" | "None" | "Ok" | "Err") {
125                    continue;
126                }
127                let cps = ctor_params(self.ctx, ctor_ty, 0, name);
128                if cps.is_empty() {
129                    self.output.push_str(&format!(
130                        "pub const {c}: {n} = {n}::{c};\n",
131                        c = ctor_name, n = name,
132                    ));
133                } else {
134                    let sig: Vec<String> =
135                        cps.iter().enumerate().map(|(i, (t, _))| format!("a{i}: {t}")).collect();
136                    let args: Vec<String> = cps
137                        .iter()
138                        .enumerate()
139                        .map(|(i, (_, rec))| if *rec { format!("Box::new(a{i})") } else { format!("a{i}") })
140                        .collect();
141                    self.output.push_str(&format!(
142                        "pub fn {c}({sig}) -> {n} {{ {n}::{c}({args}) }}\n",
143                        c = ctor_name, n = name, sig = sig.join(", "), args = args.join(", "),
144                    ));
145                }
146            }
147            self.output.push('\n');
148        }
149        Ok(())
150    }
151
152    /// Emit a definition as a Rust function or constant.
153    pub fn emit_definition(&mut self, name: &str) -> Result<(), ExtractError> {
154        if self.emitted.contains(name) {
155            return Ok(());
156        }
157        self.emitted.insert(name.to_string());
158
159        let body = self
160            .ctx
161            .get_definition_body(name)
162            .ok_or_else(|| ExtractError::NotFound(name.to_string()))?;
163        let ty = self
164            .ctx
165            .get_definition_type(name)
166            .ok_or_else(|| ExtractError::NotFound(name.to_string()))?;
167
168        // Check if it's a fixpoint (recursive function)
169        if let Term::Fix {
170            name: rec_name,
171            body: fix_body,
172        } = body
173        {
174            self.emit_fix_as_fn(name, rec_name, fix_body, ty)?;
175        } else if is_lambda(body) {
176            self.emit_lambda_as_fn(name, body, ty)?;
177        } else {
178            // A value definition. Emitted as a nullary function rather than a
179            // `const` because constructor values allocate (`Box::new`), which is
180            // not allowed in `const` context.
181            self.emit_value_fn(name, body, ty)?;
182        }
183        Ok(())
184    }
185
186    /// Emit a fixpoint as a recursive Rust function.
187    fn emit_fix_as_fn(
188        &mut self,
189        def_name: &str,
190        rec_name: &str,
191        fix_body: &Term,
192        ty: &Term,
193    ) -> Result<(), ExtractError> {
194        // Extract parameters from nested lambdas
195        let (params, inner_body) = extract_lambda_params(fix_body);
196
197        // Extract parameter types from the Pi type
198        let param_types = extract_pi_params(ty);
199
200        // Get return type
201        let ret_ty = extract_return_type(ty);
202
203        // Build function signature
204        self.output.push_str(&format!("pub fn {}(", def_name));
205        for (i, (param_name, _)) in params.iter().enumerate() {
206            if i > 0 {
207                self.output.push_str(", ");
208            }
209            let param_ty = param_types
210                .get(i)
211                .map(|(_, t)| type_to_rust(t))
212                .unwrap_or_else(|| "()".to_string());
213            self.output.push_str(&format!("{}: {}", param_name, param_ty));
214        }
215        self.output
216            .push_str(&format!(") -> {} {{\n", type_to_rust(&ret_ty)));
217
218        // Generate body, replacing recursive calls
219        let body_code = self.term_to_rust(inner_body, def_name, rec_name);
220        self.output.push_str(&format!("    {}\n", body_code));
221        self.output.push_str("}\n\n");
222
223        Ok(())
224    }
225
226    /// Emit a non-recursive lambda as a Rust function.
227    fn emit_lambda_as_fn(
228        &mut self,
229        def_name: &str,
230        body: &Term,
231        ty: &Term,
232    ) -> Result<(), ExtractError> {
233        // Extract parameters from nested lambdas
234        let (params, inner_body) = extract_lambda_params(body);
235
236        // Extract parameter types from the Pi type
237        let param_types = extract_pi_params(ty);
238
239        // Get return type
240        let ret_ty = extract_return_type(ty);
241
242        // Build function signature
243        self.output.push_str(&format!("pub fn {}(", def_name));
244        for (i, (param_name, _)) in params.iter().enumerate() {
245            if i > 0 {
246                self.output.push_str(", ");
247            }
248            let param_ty = param_types
249                .get(i)
250                .map(|(_, t)| type_to_rust(t))
251                .unwrap_or_else(|| "()".to_string());
252            self.output.push_str(&format!("{}: {}", param_name, param_ty));
253        }
254        self.output
255            .push_str(&format!(") -> {} {{\n", type_to_rust(&ret_ty)));
256
257        // Generate body (no recursive name replacement)
258        let body_code = self.term_to_rust(inner_body, def_name, "");
259        self.output.push_str(&format!("    {}\n", body_code));
260        self.output.push_str("}\n\n");
261
262        Ok(())
263    }
264
265    /// Emit a value definition as a nullary function.
266    ///
267    /// `Definition one : Nat := Succ Zero` becomes `fn one() -> Nat { … }`. A
268    /// function (not a `const`) because the body may allocate (`Box::new`).
269    fn emit_value_fn(&mut self, name: &str, body: &Term, ty: &Term) -> Result<(), ExtractError> {
270        let ty_str = type_to_rust(ty);
271        let body_str = self.term_to_rust(body, name, "");
272        self.output
273            .push_str(&format!("pub fn {}() -> {} {{\n    {}\n}}\n\n", name, ty_str, body_str));
274        Ok(())
275    }
276
277    /// Convert a kernel term to Rust code.
278    fn term_to_rust(&self, term: &Term, def_name: &str, rec_name: &str) -> String {
279        self.term_to_rust_forced(term, def_name, rec_name, &HashSet::new())
280    }
281
282    /// As [`Self::term_to_rust`], but `force_clone` names are `.clone()`d at every
283    /// use even if they appear once — used when a value is shared across separately
284    /// emitted sub-terms (e.g. both sides of a property `lhs == rhs`).
285    fn term_to_rust_forced(
286        &self,
287        term: &Term,
288        def_name: &str,
289        rec_name: &str,
290        force_clone: &HashSet<String>,
291    ) -> String {
292        let empty_deref = HashSet::new();
293        // Variables used more than once must be cloned at each use (Rust moves by
294        // default; the kernel's lambda calculus does not).
295        let mut counts: HashMap<String, usize> = HashMap::new();
296        count_vars(term, &mut counts);
297        let mut multi_use: HashSet<String> = counts
298            .into_iter()
299            .filter(|(_, c)| *c > 1)
300            .map(|(name, _)| name)
301            .collect();
302        multi_use.extend(force_clone.iter().cloned());
303        let ctx = TermGenCtx {
304            def_name,
305            rec_name,
306            deref_vars: &empty_deref,
307            multi_use: &multi_use,
308        };
309        self.term_to_rust_ctx(term, &ctx)
310    }
311
312    /// Convert a kernel term to Rust code with context for dereferencing.
313    fn term_to_rust_ctx(&self, term: &Term, tctx: &TermGenCtx) -> String {
314        match term {
315            // A universe-polymorphic reference extracts by its name; universe arguments
316            // carry no runtime content.
317            Term::Const { name, .. } => name.clone(),
318            Term::Var(name) => {
319                // Check if this is a reference to the recursive function
320                if name == tctx.rec_name && !tctx.rec_name.is_empty() {
321                    tctx.def_name.to_string()
322                } else {
323                    // Dereference boxed bindings from recursive match patterns.
324                    let base = if tctx.deref_vars.contains(name) {
325                        format!("(*{})", name)
326                    } else {
327                        name.clone()
328                    };
329                    // Clone variables used more than once so non-linear use type-checks.
330                    if tctx.multi_use.contains(name) {
331                        format!("{}.clone()", base)
332                    } else {
333                        base
334                    }
335                }
336            }
337            Term::Global(name) => {
338                // Check if it's a constructor
339                if self.ctx.is_constructor(name) {
340                    if let Some(ind) = self.ctx.constructor_inductive(name) {
341                        return format!("{}::{}", ind, name);
342                    }
343                }
344                // Check if it's the recursive reference
345                if name == tctx.rec_name && !tctx.rec_name.is_empty() {
346                    return tctx.def_name.to_string();
347                }
348                // A value definition is emitted as a nullary `fn`, so reference it
349                // by calling it.
350                if is_value_def(self.ctx, name) {
351                    return format!("{}()", name);
352                }
353                name.clone()
354            }
355            Term::App(_, _) => {
356                // Collect the head function and all arguments
357                let (head, args) = collect_app_chain(term);
358                let args_strs: Vec<String> = args
359                    .iter()
360                    .map(|a| self.term_to_rust_ctx(a, tctx))
361                    .collect();
362
363                // Check if the head is a constructor
364                if let Term::Global(name) = head {
365                    if self.ctx.is_constructor(name) {
366                        if let Some(ind) = self.ctx.constructor_inductive(name) {
367                            // Erase the leading type arguments (they correspond to
368                            // the inductive's type parameters) and box each field
369                            // whose type is recursive.
370                            let n_params = type_params(self.ctx, ind).len();
371                            let value_args: &[String] = if args_strs.len() >= n_params {
372                                &args_strs[n_params..]
373                            } else {
374                                &args_strs
375                            };
376                            let rec = ctor_field_recursion(self.ctx, self.ctx.get_global(name), ind, n_params);
377                            let fields: Vec<String> = value_args
378                                .iter()
379                                .enumerate()
380                                .map(|(i, a)| {
381                                    if rec.get(i).copied().unwrap_or(false) {
382                                        format!("Box::new({})", a)
383                                    } else {
384                                        a.clone()
385                                    }
386                                })
387                                .collect();
388                            if fields.is_empty() {
389                                return format!("{}::{}", ind, name);
390                            } else {
391                                return format!("{}::{}({})", ind, name, fields.join(", "));
392                            }
393                        }
394                    }
395                }
396
397                // Arithmetic builtins (`add`/`sub`/`mul`/`div`/`mod` : Int -> Int ->
398                // Int) are opaque kernel declarations whose computational behavior IS
399                // the Rust integer operator — their kernel laws (add_comm/add_assoc/
400                // add_zero, …) are exactly `+`'s. Emit the operator so primitive proven
401                // functions (`fun n => add n n`) extract to real, runnable Rust rather
402                // than a call to an undefined `add`. CRITICAL: only the BUILTIN (a
403                // bodyless declaration) maps to an operator — a user-defined function
404                // that happens to be named `add` (e.g. Peano `add : Num -> Num -> Num`,
405                // which HAS a body) is extracted as a real `fn` and must be CALLED.
406                if let Term::Global(name) = head {
407                    let is_builtin_op = self.ctx.get_definition_body(name).is_none()
408                        && !self.ctx.is_constructor(name)
409                        && !self.ctx.is_inductive(name);
410                    if is_builtin_op {
411                        if let Some(op) = arith_operator(name) {
412                            if args_strs.len() == 2 {
413                                return format!("({} {} {})", args_strs[0], op, args_strs[1]);
414                            }
415                        }
416                    }
417                }
418
419                // Check if head is a Var that should be renamed (recursive call)
420                let head_str = if let Term::Var(name) = head {
421                    if name == tctx.rec_name && !tctx.rec_name.is_empty() {
422                        tctx.def_name.to_string()
423                    } else {
424                        name.clone()
425                    }
426                } else {
427                    self.term_to_rust_ctx(head, tctx)
428                };
429
430                // Regular function call with all arguments comma-separated
431                format!("{}({})", head_str, args_strs.join(", "))
432            }
433            Term::Lambda {
434                param,
435                param_type,
436                body,
437            } => {
438                let param_ty = type_to_rust(param_type);
439                let body_str = self.term_to_rust_ctx(body, tctx);
440                format!("|{}: {}| {}", param, param_ty, body_str)
441            }
442            Term::Let {
443                name, value, body, ..
444            } => {
445                let value_str = self.term_to_rust_ctx(value, tctx);
446                let body_str = self.term_to_rust_ctx(body, tctx);
447                format!("{{ let {} = {}; {} }}", name, value_str, body_str)
448            }
449            Term::MutualFix { defs, index } => {
450                // Extract the definition this occurrence denotes (best-effort: mutual recursion
451                // is emitted as the selected body; a full extraction would hoist all defs).
452                match defs.get(*index) {
453                    Some((_, body)) => self.term_to_rust_ctx(body, tctx),
454                    None => "()".to_string(),
455                }
456            }
457            Term::Match {
458                discriminant,
459                motive,
460                cases,
461            } => {
462                let disc_str = self.term_to_rust_ctx(discriminant, tctx);
463
464                // Get the inductive type from the motive (λx:T. ReturnType)
465                // The param_type of the motive lambda gives us the inductive
466                let ind_name = self.infer_inductive_from_motive(motive)
467                    .or_else(|| self.infer_inductive_type(discriminant));
468
469                let mut result = format!("match {} {{\n", disc_str);
470                if let Some(ind) = &ind_name {
471                    let ctors = distinct_constructors(self.ctx, ind);
472                    let n_params = type_params(self.ctx, ind).len();
473
474                    for (i, (ctor_name, ctor_ty)) in ctors.iter().enumerate() {
475                        if i < cases.len() {
476                            let case = &cases[i];
477                            // Type parameters are erased, so they are not pattern fields.
478                            let ctor_arity = count_ctor_args(ctor_ty).saturating_sub(n_params);
479
480                            result.push_str(&format!("        {}::{}", ind, ctor_name));
481                            if ctor_arity > 0 {
482                                // Generate pattern with bindings
483                                let (bindings, case_body) = extract_case_bindings(case, ctor_arity);
484                                result.push_str("(");
485                                for (j, binding) in bindings.iter().enumerate() {
486                                    if j > 0 {
487                                        result.push_str(", ");
488                                    }
489                                    result.push_str(binding);
490                                }
491                                result.push_str(")");
492
493                                // Only bindings for BOXED fields are dereferenced in the
494                                // body — a per-field mask (not all-or-nothing), so a
495                                // non-recursive field (e.g. an `Int` beside a recursive
496                                // field) is not wrongly `(*x)`-derefed, and mutual-
497                                // recursion's boxed cross-fields ARE.
498                                let box_mask = ctor_field_recursion(self.ctx, Some(ctor_ty), ind, n_params);
499                                let case_deref_vars: HashSet<String> = bindings
500                                    .iter()
501                                    .enumerate()
502                                    .filter(|(j, _)| box_mask.get(*j).copied().unwrap_or(false))
503                                    .map(|(_, b)| b.clone())
504                                    .collect();
505                                let case_tctx = TermGenCtx {
506                                    def_name: tctx.def_name,
507                                    rec_name: tctx.rec_name,
508                                    deref_vars: &case_deref_vars,
509                                    multi_use: tctx.multi_use,
510                                };
511                                let body_str = self.term_to_rust_ctx(&case_body, &case_tctx);
512                                result.push_str(&format!(" => {},\n", body_str));
513                            } else {
514                                let case_str = self.term_to_rust_ctx(case, tctx);
515                                result.push_str(&format!(" => {},\n", case_str));
516                            }
517                        }
518                    }
519                }
520                result.push_str("    }");
521                result
522            }
523            Term::Fix { name, body } => {
524                // Inline fixpoints are tricky - for now, just extract the body
525                let fix_tctx = TermGenCtx {
526                    def_name: tctx.def_name,
527                    rec_name: name,
528                    deref_vars: tctx.deref_vars,
529                    multi_use: tctx.multi_use,
530                };
531                self.term_to_rust_ctx(body, &fix_tctx)
532            }
533            Term::Lit(lit) => match lit {
534                Literal::Int(n) => format!("{}i64", n),
535                Literal::BigInt(n) => format!("{}i128 /* bigint */", n),
536                Literal::Nat(n) => format!("{}u64 /* nat */", n),
537                Literal::Float(f) => format!("{}f64", f),
538                Literal::Text(s) => format!("{:?}", s),
539                Literal::Duration(nanos) => format!("{}i64 /* nanos */", nanos),
540                Literal::Date(days) => format!("{}i32 /* days since epoch */", days),
541                Literal::Moment(nanos) => format!("{}i64 /* nanos since epoch */", nanos),
542            },
543            Term::Pi { .. } => "/* type */".to_string(),
544            Term::Sort(_) => "/* sort */".to_string(),
545            Term::Hole => "_".to_string(), // Type placeholder
546        }
547    }
548
549    /// Extract the inductive type from a match motive.
550    ///
551    /// The motive is typically `λx:T. ReturnType` where T is the inductive.
552    fn infer_inductive_from_motive(&self, motive: &Term) -> Option<String> {
553        if let Term::Lambda { param_type, .. } = motive {
554            if let Term::Global(name) = param_type.as_ref() {
555                if self.ctx.is_inductive(name) {
556                    return Some(name.clone());
557                }
558            }
559        }
560        None
561    }
562
563    /// Try to infer the inductive type from a term.
564    fn infer_inductive_type(&self, term: &Term) -> Option<String> {
565        match term {
566            Term::Var(_) => {
567                // Cannot infer from Var alone - use motive instead
568                None
569            }
570            Term::Global(name) => {
571                if self.ctx.is_constructor(name) {
572                    self.ctx.constructor_inductive(name).map(|s| s.to_string())
573                } else if self.ctx.is_inductive(name) {
574                    Some(name.clone())
575                } else {
576                    None
577                }
578            }
579            Term::App(f, _) => self.infer_inductive_type(f),
580            Term::Hole => None, // Holes are type placeholders
581            _ => None,
582        }
583    }
584}
585
586/// Count how many times each variable is referenced in a term.
587///
588/// Used to decide which bindings must be `.clone()`d at their use sites: a value
589/// used more than once cannot be moved each time under Rust's ownership rules.
590fn count_vars(term: &Term, counts: &mut HashMap<String, usize>) {
591    match term {
592        Term::Const { .. } => {}
593        Term::Var(name) => {
594            *counts.entry(name.clone()).or_insert(0) += 1;
595        }
596        Term::App(f, a) => {
597            count_vars(f, counts);
598            count_vars(a, counts);
599        }
600        Term::Lambda {
601            param_type, body, ..
602        } => {
603            count_vars(param_type, counts);
604            count_vars(body, counts);
605        }
606        Term::Let {
607            ty, value, body, ..
608        } => {
609            count_vars(ty, counts);
610            count_vars(value, counts);
611            count_vars(body, counts);
612        }
613        Term::MutualFix { defs, .. } => {
614            for (_, body) in defs {
615                count_vars(body, counts);
616            }
617        }
618        Term::Pi {
619            param_type,
620            body_type,
621            ..
622        } => {
623            count_vars(param_type, counts);
624            count_vars(body_type, counts);
625        }
626        Term::Fix { body, .. } => count_vars(body, counts),
627        Term::Match {
628            discriminant,
629            motive,
630            cases,
631        } => {
632            count_vars(discriminant, counts);
633            count_vars(motive, counts);
634            for case in cases {
635                count_vars(case, counts);
636            }
637        }
638        Term::Sort(_) | Term::Global(_) | Term::Lit(_) | Term::Hole => {}
639    }
640}
641
642/// Emit a (normalized) data-value `Term` as a Rust expression — used by the
643/// self-verifying demo `main` to write the kernel-computed expected result.
644pub fn emit_value(ctx: &Context, term: &Term) -> String {
645    CodeGen::new(ctx).term_to_rust(term, "", "")
646}
647
648/// Emit a runnable property check `fn check_<name>(params) -> bool { … }` from a
649/// theorem's PROPOSITION (its kernel type), or `None` if the statement isn't a
650/// finite, executable property over extractable functions. A proven theorem like
651/// `∀n. Eq Nat (add Zero n) n` becomes `fn check_…(n: Nat) -> bool { add(Nat::Zero, n) == n }`.
652pub fn emit_property_check(ctx: &Context, name: &str, prop: &Term) -> Option<String> {
653    // Peel `forall` (Pi) binders into parameters over data types.
654    let mut params: Vec<(String, String)> = Vec::new();
655    let mut cur = prop;
656    while let Term::Pi { param, param_type, body_type } = cur {
657        if !prop_type_is_data(ctx, param_type) {
658            return None; // quantifies over Prop/opaque — not runnable
659        }
660        params.push((param.clone(), type_to_rust(param_type)));
661        cur = body_type;
662    }
663    if params.is_empty() {
664        // No quantifier — only worthwhile when it's a closed equation; allow it.
665    }
666    let param_names: HashSet<String> = params.iter().map(|(n, _)| n.clone()).collect();
667    let body = emit_prop_body(ctx, cur, &param_names)?;
668    let sig: Vec<String> = params.iter().map(|(n, t)| format!("{}: {}", n, t)).collect();
669    Some(format!(
670        "/// Runnable property from the proven theorem `{name}`.\n\
671         pub fn check_{name}({}) -> bool {{\n    {}\n}}\n\n",
672        sig.join(", "),
673        body
674    ))
675}
676
677/// A concrete data type (mapped primitive, or user inductive with constructors).
678fn prop_type_is_data(ctx: &Context, ty: &Term) -> bool {
679    match ty {
680        Term::Global(n) => {
681            matches!(n.as_str(), "Int" | "Float" | "Text" | "Bool" | "Duration" | "Date" | "Moment")
682                || (ctx.is_inductive(n) && !ctx.get_constructors(n).is_empty())
683        }
684        Term::App(_, _) => {
685            let (h, args) = collect_app_chain(ty);
686            prop_type_is_data(ctx, h) && args.iter().all(|a| prop_type_is_data(ctx, a))
687        }
688        _ => false,
689    }
690}
691
692/// A proposition body → a Rust `bool` expression (`Eq`→`==`, `And`/`Or`/`Not`),
693/// or `None` if not a finitely-checkable shape.
694fn emit_prop_body(ctx: &Context, p: &Term, params: &HashSet<String>) -> Option<String> {
695    let (head, args) = collect_app_chain(p);
696    if let Term::Global(h) = head {
697        match (h.as_str(), args.len()) {
698            ("Eq", 3) => {
699                let lhs = emit_checkable_term(ctx, args[1], params)?;
700                let rhs = emit_checkable_term(ctx, args[2], params)?;
701                return Some(format!("({} == {})", lhs, rhs));
702            }
703            ("And", 2) => {
704                return Some(format!(
705                    "({} && {})",
706                    emit_prop_body(ctx, args[0], params)?,
707                    emit_prop_body(ctx, args[1], params)?
708                ))
709            }
710            ("Or", 2) => {
711                return Some(format!(
712                    "({} || {})",
713                    emit_prop_body(ctx, args[0], params)?,
714                    emit_prop_body(ctx, args[1], params)?
715                ))
716            }
717            ("Not", 1) => return Some(format!("(!{})", emit_prop_body(ctx, args[0], params)?)),
718            _ => {}
719        }
720    }
721    None
722}
723
724/// One side of an equation → Rust, requiring it reference only extractable things
725/// (so the check compiles), with quantified `params` cloned (shared across sides).
726fn emit_checkable_term(ctx: &Context, term: &Term, params: &HashSet<String>) -> Option<String> {
727    let mut refs = Vec::new();
728    super::collector::collect_globals(term, &mut refs);
729    if !refs.iter().all(|g| g == "_" || crate::extraction::is_extractable(ctx, g)) {
730        return None;
731    }
732    Some(CodeGen::new(ctx).term_to_rust_forced(term, "", "", params))
733}
734
735
736/// Constructors of an inductive, de-duplicated by name (keeping the first of each).
737///
738/// Redefining an inductive that already exists (e.g. the StandardLibrary `Nat`)
739/// appends its constructors again to the registration order; emitting those
740/// verbatim would produce an enum with duplicate variants. Dedup by name so the
741/// extracted enum is valid.
742fn distinct_constructors<'a>(ctx: &'a Context, inductive: &str) -> Vec<(&'a str, &'a Term)> {
743    let mut seen = HashSet::new();
744    ctx.get_constructors(inductive)
745        .into_iter()
746        .filter(|(n, _)| seen.insert(n.to_string()))
747        .collect()
748}
749
750/// The type parameter names of an inductive (e.g. `["A"]` for `MyList (A : Type)`).
751///
752/// They are encoded as leading Π-binders over a `Sort` on the inductive's own
753/// type, so we read them off the inductive's sort.
754fn type_params(ctx: &Context, inductive: &str) -> Vec<String> {
755    let mut names = Vec::new();
756    let mut current = match ctx.get_global(inductive) {
757        Some(ty) => ty,
758        None => return names,
759    };
760    while let Term::Pi {
761        param,
762        param_type,
763        body_type,
764    } = current
765    {
766        // Only leading `: Type` binders are type parameters (→ Rust generics).
767        if matches!(param_type.as_ref(), Term::Sort(_)) {
768            names.push(param.clone());
769            current = body_type;
770        } else {
771            break;
772        }
773    }
774    names
775}
776
777/// Extract a constructor's Rust field types, skipping the `skip` leading type
778/// parameters and boxing any field whose type is recursive (mentions the
779/// inductive) to keep the enum finitely sized.
780/// Collect the inductive-type names referenced anywhere in a (field) type term.
781fn collect_inductive_globals(ctx: &Context, term: &Term, out: &mut Vec<String>) {
782    match term {
783        Term::Global(n) => {
784            if ctx.is_inductive(n) {
785                out.push(n.clone());
786            }
787        }
788        Term::App(f, a) => {
789            collect_inductive_globals(ctx, f, out);
790            collect_inductive_globals(ctx, a, out);
791        }
792        Term::Pi { param_type, body_type, .. } => {
793            collect_inductive_globals(ctx, param_type, out);
794            collect_inductive_globals(ctx, body_type, out);
795        }
796        _ => {}
797    }
798}
799
800/// The inductive types referenced by a constructor's FIELDS (its Pi params after the
801/// inductive's leading type parameters) — the out-edges of an inductive in the type
802/// graph.
803fn ctor_field_inductives(ctx: &Context, ctor_ty: &Term, skip: usize, out: &mut Vec<String>) {
804    let mut cur = ctor_ty;
805    for _ in 0..skip {
806        if let Term::Pi { body_type, .. } = cur {
807            cur = body_type;
808        } else {
809            break;
810        }
811    }
812    while let Term::Pi { param_type, body_type, .. } = cur {
813        collect_inductive_globals(ctx, param_type, out);
814        cur = body_type;
815    }
816}
817
818/// Whether inductive `from` can reach `to` by following constructor-field references
819/// (`X → Y` iff a constructor of `X` has a field referencing inductive `Y`). Self- and
820/// mutual-recursion both show up as `reaches(T, T)` / mutual reachability.
821fn inductive_reaches(ctx: &Context, from: &str, to: &str) -> bool {
822    let mut visited: HashSet<String> = HashSet::new();
823    let mut stack = vec![from.to_string()];
824    while let Some(x) = stack.pop() {
825        let skip = type_params(ctx, &x).len();
826        for (_, cty) in distinct_constructors(ctx, &x) {
827            let mut refs = Vec::new();
828            ctor_field_inductives(ctx, cty, skip, &mut refs);
829            for r in refs {
830                if r == to {
831                    return true;
832                }
833                if visited.insert(r.clone()) {
834                    stack.push(r);
835                }
836            }
837        }
838    }
839    false
840}
841
842/// Whether a constructor field of type `field_type` (inside inductive `enclosing`)
843/// must be `Box`ed: it references some inductive that reaches back to `enclosing`,
844/// closing a recursion cycle — self-recursion OR a mutual-recursion group. Acyclic
845/// inductive fields stay unboxed (no over-boxing), so single-recursion output is
846/// unchanged; only genuine cycles (incl. mutual recursion) gain the needed `Box`.
847fn field_boxed(ctx: &Context, field_type: &Term, enclosing: &str) -> bool {
848    let mut refs = Vec::new();
849    collect_inductive_globals(ctx, field_type, &mut refs);
850    refs.iter().any(|t| inductive_reaches(ctx, t, enclosing))
851}
852
853fn extract_ctor_fields(ctx: &Context, ty: &Term, inductive: &str, skip: usize) -> Vec<String> {
854    let mut current = ty;
855    for _ in 0..skip {
856        if let Term::Pi { body_type, .. } = current {
857            current = body_type;
858        } else {
859            break;
860        }
861    }
862
863    let mut fields = Vec::new();
864    while let Term::Pi {
865        param_type,
866        body_type,
867        ..
868    } = current
869    {
870        let field_ty = type_to_rust(param_type);
871        if field_boxed(ctx, param_type, inductive) {
872            fields.push(format!("Box<{}>", field_ty));
873        } else {
874            fields.push(field_ty);
875        }
876        current = body_type;
877    }
878    fields
879}
880
881/// Which of a constructor's value fields are recursive (mention the inductive),
882/// after skipping `skip` leading type parameters. Parallel to the field list
883/// produced by [`extract_ctor_fields`] — used to decide where to `Box::new`.
884/// A constructor's parameters as `(unboxed_rust_type, is_recursive)`, after skipping
885/// the inductive's leading type parameters. Used to emit constructor-wrapper fns: the
886/// wrapper takes the UNBOXED field type and boxes recursive fields itself.
887fn ctor_params(ctx: &Context, ty: &Term, skip: usize, inductive: &str) -> Vec<(String, bool)> {
888    let mut current = ty;
889    for _ in 0..skip {
890        if let Term::Pi { body_type, .. } = current {
891            current = body_type;
892        } else {
893            break;
894        }
895    }
896    let mut out = Vec::new();
897    while let Term::Pi { param_type, body_type, .. } = current {
898        out.push((type_to_rust(param_type), field_boxed(ctx, param_type, inductive)));
899        current = body_type;
900    }
901    out
902}
903
904fn ctor_field_recursion(ctx: &Context, ty: Option<&Term>, inductive: &str, skip: usize) -> Vec<bool> {
905    let mut flags = Vec::new();
906    let mut current = match ty {
907        Some(t) => t,
908        None => return flags,
909    };
910    for _ in 0..skip {
911        if let Term::Pi { body_type, .. } = current {
912            current = body_type;
913        } else {
914            break;
915        }
916    }
917    while let Term::Pi {
918        param_type,
919        body_type,
920        ..
921    } = current
922    {
923        flags.push(field_boxed(ctx, param_type, inductive));
924        current = body_type;
925    }
926    flags
927}
928
929/// A value definition is one whose body is data (not a function): it is emitted
930/// as a nullary `fn` and referenced by calling it.
931fn is_value_def(ctx: &Context, name: &str) -> bool {
932    ctx.get_definition_body(name)
933        .map(|b| !matches!(b, Term::Lambda { .. } | Term::Fix { .. }))
934        .unwrap_or(false)
935}
936
937/// The canonical kernel-primitive → Rust-type bridge: the SINGLE source of truth
938/// for how the seven opaque kernel primitives lower to Rust. Both the extractor and
939/// any caller that reasons about extractable types consult this, so they can never
940/// disagree on a primitive's Rust type — the soundness contract that lets imperative
941/// code call a bundled proven function over primitives. `None` means the name is not
942/// a primitive (it may still be a user inductive or a generic type variable). These
943/// are registered in the StandardLibrary as constructorless inductives, so they have
944/// no enum form and are represented directly by their Rust counterpart.
945pub fn primitive_rust_type(name: &str) -> Option<&'static str> {
946    Some(match name {
947        "Int" => "i64",
948        "Float" => "f64",
949        "Text" => "String",
950        "Bool" => "bool",
951        "Duration" => "i64",
952        "Date" => "i32",
953        "Moment" => "i64",
954        _ => return None,
955    })
956}
957
958/// Whether every use of an arithmetic builtin in `term` is a full BINARY application
959/// — the only form that lowers to a Rust operator. A bare or partially-applied builtin
960/// (`add`, `add n`) has no Rust definition to call, so a definition containing one is
961/// NOT cleanly extractable (it would emit a call to an undefined `add`). `term_to_rust`
962/// only handles the 2-arg case; this gate keeps such definitions out of extraction.
963pub(crate) fn arith_uses_well_formed(term: &Term) -> bool {
964    match term {
965        // A builtin appearing anywhere except as the head of a 2-arg application.
966        Term::Global(name) => arith_operator(name).is_none(),
967        Term::App(_, _) => {
968            let (head, args) = collect_app_chain(term);
969            if let Term::Global(name) = head {
970                if arith_operator(name).is_some() {
971                    // Head is a builtin: require exactly 2 args, and they must be clean.
972                    return args.len() == 2 && args.iter().all(|a| arith_uses_well_formed(a));
973                }
974            }
975            // Non-builtin head: head and every arg must be clean.
976            arith_uses_well_formed(head) && args.iter().all(|a| arith_uses_well_formed(a))
977        }
978        Term::Lambda { param_type, body, .. } => {
979            arith_uses_well_formed(param_type) && arith_uses_well_formed(body)
980        }
981        Term::Pi { param_type, body_type, .. } => {
982            arith_uses_well_formed(param_type) && arith_uses_well_formed(body_type)
983        }
984        Term::Fix { body, .. } => arith_uses_well_formed(body),
985        Term::Match { discriminant, motive, cases } => {
986            arith_uses_well_formed(discriminant)
987                && arith_uses_well_formed(motive)
988                && cases.iter().all(arith_uses_well_formed)
989        }
990        _ => true,
991    }
992}
993
994/// The Rust integer operator for a kernel arithmetic builtin, if any. These five
995/// are opaque `Int -> Int -> Int` declarations whose computational behavior is the
996/// corresponding Rust operator; mapping them is faithful to their kernel axioms and
997/// lets primitive proven functions extract to runnable Rust. `None` means `name` is
998/// not an arithmetic builtin.
999pub(crate) fn arith_operator(name: &str) -> Option<&'static str> {
1000    Some(match name {
1001        "add" => "+",
1002        "sub" => "-",
1003        "mul" => "*",
1004        "div" => "/",
1005        "mod" => "%",
1006        _ => return None,
1007    })
1008}
1009
1010/// Convert a kernel type to a Rust type string.
1011fn type_to_rust(ty: &Term) -> String {
1012    match ty {
1013        // A type variable (an inductive's type parameter) → a Rust generic.
1014        Term::Var(name) => name.clone(),
1015        Term::Global(name) => {
1016            primitive_rust_type(name)
1017                .map(str::to_string)
1018                .unwrap_or_else(|| name.clone())
1019        }
1020        Term::Pi {
1021            param_type,
1022            body_type,
1023            ..
1024        } => {
1025            // Non-dependent function type: A -> B
1026            let arg = type_to_rust(param_type);
1027            let ret = type_to_rust(body_type);
1028            format!("fn({}) -> {}", arg, ret)
1029        }
1030        Term::App(_, _) => {
1031            // A generic instantiation like `MyList Nat` or `MyPair A B`. Flatten
1032            // the application chain into `Head<arg0, arg1, …>`.
1033            let (head, args) = collect_app_chain(ty);
1034            let head_str = type_to_rust(head);
1035            let arg_strs: Vec<String> = args.iter().map(|a| type_to_rust(a)).collect();
1036            format!("{}<{}>", head_str, arg_strs.join(", "))
1037        }
1038        Term::Sort(_) => "()".to_string(),
1039        Term::Lit(_) => "()".to_string(), // Literals shouldn't appear as types
1040        _ => "()".to_string(),
1041    }
1042}
1043
1044/// Check if a term is a lambda.
1045fn is_lambda(term: &Term) -> bool {
1046    matches!(term, Term::Lambda { .. })
1047}
1048
1049/// Extract parameters from nested lambdas.
1050fn extract_lambda_params(term: &Term) -> (Vec<(String, Term)>, &Term) {
1051    let mut params = Vec::new();
1052    let mut current = term;
1053
1054    while let Term::Lambda {
1055        param,
1056        param_type,
1057        body,
1058    } = current
1059    {
1060        params.push((param.clone(), (**param_type).clone()));
1061        current = body;
1062    }
1063
1064    (params, current)
1065}
1066
1067/// Extract parameter types from a Pi type.
1068fn extract_pi_params(ty: &Term) -> Vec<(String, Term)> {
1069    let mut params = Vec::new();
1070    let mut current = ty;
1071
1072    while let Term::Pi {
1073        param,
1074        param_type,
1075        body_type,
1076    } = current
1077    {
1078        params.push((param.clone(), (**param_type).clone()));
1079        current = body_type;
1080    }
1081
1082    params
1083}
1084
1085/// Extract the return type from a (possibly nested) Pi type.
1086fn extract_return_type(ty: &Term) -> Term {
1087    let mut current = ty;
1088    while let Term::Pi { body_type, .. } = current {
1089        current = body_type;
1090    }
1091    current.clone()
1092}
1093
1094/// Count the number of arguments a constructor takes.
1095fn count_ctor_args(ty: &Term) -> usize {
1096    let mut count = 0;
1097    let mut current = ty;
1098    while let Term::Pi { body_type, .. } = current {
1099        count += 1;
1100        current = body_type;
1101    }
1102    count
1103}
1104
1105/// Extract bindings from a case (which is typically a lambda).
1106fn extract_case_bindings(case: &Term, arity: usize) -> (Vec<String>, Term) {
1107    let mut bindings = Vec::new();
1108    let mut current = case;
1109
1110    for _ in 0..arity {
1111        if let Term::Lambda { param, body, .. } = current {
1112            bindings.push(param.clone());
1113            current = body;
1114        } else {
1115            break;
1116        }
1117    }
1118
1119    (bindings, current.clone())
1120}
1121
1122/// Collect the head and all arguments from a chain of applications.
1123///
1124/// For `((f a) b) c`, returns `(f, [a, b, c])`.
1125fn collect_app_chain(term: &Term) -> (&Term, Vec<&Term>) {
1126    let mut args = Vec::new();
1127    let mut current = term;
1128
1129    while let Term::App(f, a) = current {
1130        args.push(a.as_ref());
1131        current = f.as_ref();
1132    }
1133
1134    // Reverse to get args in application order
1135    args.reverse();
1136    (current, args)
1137}
1138
1139/// Check if a term is a constructor application.
1140#[allow(dead_code)]
1141fn is_constructor_app(term: &Term, ctx: &Context) -> bool {
1142    match term {
1143        Term::Global(name) => ctx.is_constructor(name),
1144        Term::App(f, _) => is_constructor_app(f, ctx),
1145        _ => false,
1146    }
1147}