From 559b408a94c1d8c51766708aae89827cab9f9855 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 15:33:52 +0000 Subject: [PATCH 01/41] =?UTF-8?q?#89=20stage=203:=20register=20SP-15=20(ce?= =?UTF-8?q?rtificate=20A=20as=20kernel-term=20data):=20register=20row,=20S?= =?UTF-8?q?PEC=20=C2=A731=20row,=20scenario,=20and=20the=20failing=20named?= =?UTF-8?q?=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- CONFORMANCE.md | 1 + README.md | 4 ++-- SPEC.md | 3 ++- crates/conformance/src/cases/repository.rs | 2 +- crates/conformance/tests/conformance.rs | 5 +++++ features/suites/preservation.feature | 8 ++++++++ model/ids.toml | 6 ++++++ xtask/src/spec_links.rs | 4 ++-- 8 files changed, 27 insertions(+), 6 deletions(-) diff --git a/CONFORMANCE.md b/CONFORMANCE.md index 80a73ac45..b5f461bad 100644 --- a/CONFORMANCE.md +++ b/CONFORMANCE.md @@ -339,6 +339,7 @@ the error registry does not sanction (R5, `ERRORS.md`). | `SP-12` | `build` | The preservation library is LexLean source: every module of its registry is one language-1.2 native core module, the library project holds no file of Lean source, and no Lean file anywhere in the release tree declares its namespace or lies under its module root; the registry lists only the modules, and the declarations, their exact axiom policies, the constructors of the correspondence, and the cases of its soundness theorem are read from the native data, which the compiler embeds, hashes into the language-1.2 semantics, and generates the Lean of every verification from. | | `SP-13` | `build` | Certificate text writes every list and array of any length as a term the pinned Lean elaborates without binders: one of at most 32 elements as a literal and a longer one as a chain of List.cons, so that no certificate holds a literal Lean splits under let binders; a root calling a function of 33 parameters with 33 operands, a root copying a record of 33 fields, and a root matching on 33 constructors of one field each verify through the whole pipeline, and none of their certificates writes a literal of more than 32 elements. | | `SP-14` | `build` | Certificate E of every production root is kernel-term data: a native core module of schema lexlean/core-module/2 that the compiler builds from the lowered program, the layout, and the recorded types of the preservation library's lemmas, every argument and universe level explicit, which the one core backend renders to the Lean that verification compiles and whose record is the authoring notation, reading back to the data byte for byte; its theorems state what §17.17 states for the composition, each row pins its axioms exactly and the audit module prints and checks them, a certificate held as data is audited row by row and never as text (no axiom row, no forbidden constant, imports within the environment), and a planted wrong lemma, a changed statement, a forged crate reference, or an added axiom is refused. | +| `SP-15` | `build` | Certificate A of every production root is kernel-term data: a native core module of schema lexlean/core-module/2 that the compiler builds from the lowered program, the layout, the semantic source of the root's closure, and the recorded types of the preservation library's lemmas, every argument and universe level explicit, so that no Lean text template for certificate A remains in the production compiler; its program literal, encoders, width predicates, relation theorems, validators, and entry are rows of that module, which the one core backend renders to the Lean that verification compiles and whose record is the authoring notation, reading back to the data byte for byte; each theorem states, up to elaboration, the statement the text certificate stated, which an oracle over the committed corpora records, every row pins its axioms exactly, and a wrong lemma application, a changed statement, a forged program literal, or an added axiom is refused. | ## production diff --git a/README.md b/README.md index faa5ad5b8..14dad0beb 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ just vv # the complete normative acceptance gate (SPEC.md §9.2) just release # vv, then the §30 release criterion; refused until 1.0.0 ``` -All 383 registered conformance IDs are implemented and pass; `just vv` runs clean from a checkout with the pinned toolchain installed. +All 384 registered conformance IDs are implemented and pass; `just vv` runs clean from a checkout with the pinned toolchain installed. The differential against the independent generator `lean4-prod` (SPEC.md §17.19, `OR-01`..`OR-08`) acquires it at its pinned commit: from the checkout `LEXLEAN_ORACLE_DIR` names, or by fetching the commit with `git` into the Cargo target directory (`LEXLEAN_ORACLE_CACHE` overrides), and builds it with the pinned Lean and Rust toolchains. It is validation evidence only and no input to any identity. An oracle that cannot be acquired, is not the pinned files, or fails is unavailable: that fails the gate on Linux x86-64 and is printed as `NOT VALIDATED` elsewhere, and is never reported as agreement. @@ -169,7 +169,7 @@ Every row is validated by `just vv`; the IDs link the claim to its register row, | The canonical Rust backend: a closed, checked Rust AST whose every construct corresponds to the calculus; hygienic identifiers, single ownership with last-use and component moves, exact failure typing, and no hidden allocation; contiguous lists with constant-work cells; a closed runtime library boundary of stated machine primitives; deterministic packages with checked exports, a declared lint gate, and provenance | `RB-01`..`RB-09` | `build` | | GNAF requests over the calculus: an optimizer-independent complete-system universe, a machine contract that accounts every action, scalar and Pareto orders without weighting, fail-closed validation, and kernel-checked answers and authority vectors | `GN-01`..`GN-08` | `build` | | GNAF certification: certificates that prove a global optimum, an argmin, or a complete Pareto frontier over the independent universe from a premise for every system, a library proving the checker sound, closed transformation schemas, local normality kept distinct from optimality, a bridge from a representation metric to execution cost, and honest non-global statuses | `GC-01`..`GC-11` | `build` | -| Semantic preservation from the source to the calculus: a validated lowering of every production root, a kernel-checked certificate per root relating the lowered program to the source denotation with exact width predicates, a differential evaluator, a proof library that is LexLean source (native core modules migrated once from the Lean it replaces, its Lean generated by the compiler) pinned by exact axioms, certificate checking in verification, a declared Rust machine on which every rendering is evaluated, certificate B relating every rendering to its program, certificate E composing them end to end, boundary validators, and a rustc differential of the declared machine | `SP-01`..`SP-14` | `build` | +| Semantic preservation from the source to the calculus: a validated lowering of every production root, a kernel-checked certificate per root relating the lowered program to the source denotation with exact width predicates, a differential evaluator, a proof library that is LexLean source (native core modules migrated once from the Lean it replaces, its Lean generated by the compiler) pinned by exact axioms, certificate checking in verification, a declared Rust machine on which every rendering is evaluated, certificate B relating every rendering to its program, certificate E composing them end to end, boundary validators, and a rustc differential of the declared machine | `SP-01`..`SP-15` | `build` | | Language-1.2 models: content-addressed typed artifacts, contracts with sound validators, exact deterministic, rule, statistical, neural, and composite realizations, generated evidence obligations restated in Lean, checked runtime boundaries, and their identity, production, and verification | `MD-01`..`MD-12` | `build` | | The differential validation oracle: `lean4-prod` pinned by commit and content hash as validation evidence only, a corpus of every production root and one probe per runtime construct, a comparison of acceptance, contract, behavior, and resources that never compares bytes, versioned tables of known differences and of constructs outside the overlap, planted discrepancies detected, failures that are unavailable rather than validated, and its isolation from every identity | `OR-01`..`OR-08` | `build` | | The production export contract: a stable, versioned `lexlean::api::production` façade and its CLI through which a consumer compiles production roots to certified Rust packages; closed requests refused before Lean on every root, target, pin, evidence, path, and package defect; results binding every output to the source, semantic, compiler-semantics, target, compiler-input, preservation, and certificate identities with unforgeable certification; deterministic, confined, offline bundles; source-free replay; versioned schemas; each output's effects and what `recursion` asks of the host | `PX-01`..`PX-13` | `build` | diff --git a/SPEC.md b/SPEC.md index 067015e8b..a54be8cc1 100644 --- a/SPEC.md +++ b/SPEC.md @@ -9187,6 +9187,7 @@ Every row below is normative, has honesty level `build`, and MUST be copied byte | `SP-12` | `preservation` | The preservation library is LexLean source: every module of its registry is one language-1.2 native core module, the library project holds no file of Lean source, and no Lean file anywhere in the release tree declares its namespace or lies under its module root; the registry lists only the modules, and the declarations, their exact axiom policies, the constructors of the correspondence, and the cases of its soundness theorem are read from the native data, which the compiler embeds, hashes into the language-1.2 semantics, and generates the Lean of every verification from. | §17.17 | | `SP-13` | `preservation` | Certificate text writes every list and array of any length as a term the pinned Lean elaborates without binders: one of at most 32 elements as a literal and a longer one as a chain of List.cons, so that no certificate holds a literal Lean splits under let binders; a root calling a function of 33 parameters with 33 operands, a root copying a record of 33 fields, and a root matching on 33 constructors of one field each verify through the whole pipeline, and none of their certificates writes a literal of more than 32 elements. | §17.17 | | `SP-14` | `preservation` | Certificate E of every production root is kernel-term data: a native core module of schema lexlean/core-module/2 that the compiler builds from the lowered program, the layout, and the recorded types of the preservation library's lemmas, every argument and universe level explicit, which the one core backend renders to the Lean that verification compiles and whose record is the authoring notation, reading back to the data byte for byte; its theorems state what §17.17 states for the composition, each row pins its axioms exactly and the audit module prints and checks them, a certificate held as data is audited row by row and never as text (no axiom row, no forbidden constant, imports within the environment), and a planted wrong lemma, a changed statement, a forged crate reference, or an added axiom is refused. | §17.17 | +| `SP-15` | `preservation` | Certificate A of every production root is kernel-term data: a native core module of schema lexlean/core-module/2 that the compiler builds from the lowered program, the layout, the semantic source of the root's closure, and the recorded types of the preservation library's lemmas, every argument and universe level explicit, so that no Lean text template for certificate A remains in the production compiler; its program literal, encoders, width predicates, relation theorems, validators, and entry are rows of that module, which the one core backend renders to the Lean that verification compiles and whose record is the authoring notation, reading back to the data byte for byte; each theorem states, up to elaboration, the statement the text certificate stated, which an oracle over the committed corpora records, every row pins its axioms exactly, and a wrong lemma application, a changed statement, a forged program literal, or an added axiom is refused. | §17.17 | | `MD-01` | `models` | Language 1.2 artifact, contract, realization, evidence, and model declarations, the checked_apply term, the contract_violation type, and the less_than primitive belong to the closed lexlean/semantic-module/2 schema and its snapshot schema, are rejected under language 1.1 before either backend runs, and admit no member outside the closed schema, such as prompt text, a free-form description, or raw model configuration. | §17.12 | | `MD-02` | `models` | A model artifact is admitted only from a configured, content-addressed, confined project file whose SHA-256 and byte length equal its declaration and whose bytes decode under its declared closed schema to its declared type and role; a missing artifact or a digest or length mismatch fails with LLR3007, a schema, type, or role violation fails with LLR3008, and the generated Lean embeds the exact bytes with a kernel-checked theorem that the typed value is their decoding. | §17.12, §10.1, §26.3 | | `MD-03` | `models` | A contract names typed input, output, and optional state interfaces and prior proposition-valued predicates of exactly those interface signatures, and each runtime validator is an executable Boolean definition of the same signature linked to its predicate by a statement-exact soundness theorem and an optional completeness theorem that Lean restates against the fixed model semantics; any mismatch fails with LLT4006. | §17.12 | @@ -9259,7 +9260,7 @@ Every row below is normative, has honesty level `build`, and MUST be copied byte | `AU-11` | `authoring` | Malformed core notation fails closed and never panics: an unclosed delimiter, a keyword used as a name, a label that is not the number of its node, a reference to a node not yet written, a numeral with a redundant leading zero, a statement that defines no node, a nesting beyond the bound, and every single-token deletion and duplication of a committed module fail with a registered code at a position in the notation or link to a module that decodes. | §17.21.8 | | `AU-12` | `authoring` | A core module written with names for its statements and its variables reads to exactly the module its canonical notation reads to, and the canonical notation keeps the names of the binders and writes the numbers of the statements: a variable is written by the name of its binder exactly when no nearer binder has the name, a constant a binder in scope is named like is written as a string, a theorem added to the certification library by name reads to the committed module and is written as the committed bytes, and an unknown or repeated statement name and a variable given universe levels are refused with a position. | §17.21.8 | -**Total required capability IDs:** 383. +**Total required capability IDs:** 384. No row may be downgraded to `some-true` or `open`. Upstream Lean facts are ledger/authority rows, not substitutions for these build behaviors. diff --git a/crates/conformance/src/cases/repository.rs b/crates/conformance/src/cases/repository.rs index a3c48a201..04515b279 100644 --- a/crates/conformance/src/cases/repository.rs +++ b/crates/conformance/src/cases/repository.rs @@ -411,7 +411,7 @@ pub(crate) fn run(id: &str) { let model = repo_model::Model::load(&root.join("model").into_std_path_buf()) .expect("the model loads"); let table = spec_table(); - assert_eq!(table.len(), 383, "§31 has 383 rows"); + assert_eq!(table.len(), 384, "§31 has 384 rows"); assert_eq!(model.ids.id.len(), table.len(), "register row count"); for ((spec_id, spec_suite, spec_statement), row) in table.iter().zip(model.ids.id.iter()) diff --git a/crates/conformance/tests/conformance.rs b/crates/conformance/tests/conformance.rs index f3e27e264..e5f15d341 100644 --- a/crates/conformance/tests/conformance.rs +++ b/crates/conformance/tests/conformance.rs @@ -1570,6 +1570,11 @@ fn conformance_sp_14() { repo_conformance::cases::run("SP-14"); } +#[test] +fn conformance_sp_15() { + repo_conformance::cases::run("SP-15"); +} + #[test] fn conformance_md_01() { repo_conformance::cases::run("MD-01"); diff --git a/features/suites/preservation.feature b/features/suites/preservation.feature index 1ec0698ad..f7abbbe70 100644 --- a/features/suites/preservation.feature +++ b/features/suites/preservation.feature @@ -99,3 +99,11 @@ Feature: preservation When certificate E of each root is built as a core module, rendered by the core backend, recorded in the notation, and verified under the pinned Lean Then the record reads back to the module byte for byte, every row pins its axioms exactly, and the theorems elaborate, replay, and pass the audit And a wrong lemma application, a changed statement, a forged crate reference, and an added axiom are each refused + + @SP-15 @build + Scenario: Certificate A of every production root is kernel-term data: a native core module of schema lexlean/core-module/2 that the compiler builds from the lowered program, the layout, the semantic source of the root's closure, and the recorded types of the preservation library's lemmas, every argument and universe level explicit, so that no Lean text template for certificate A remains in the production compiler; its program literal, encoders, width predicates, relation theorems, validators, and entry are rows of that module, which the one core backend renders to the Lean that verification compiles and whose record is the authoring notation, reading back to the data byte for byte; each theorem states, up to elaboration, the statement the text certificate stated, which an oracle over the committed corpora records, every row pins its axioms exactly, and a wrong lemma application, a changed statement, a forged program literal, or an added axiom is refused. + Given every production root of the committed examples, the lowered programs, the semantic source of each root's closure, and the native preservation library + When certificate A of each root is built as a core module, rendered by the core backend, recorded in the notation, and verified under the pinned Lean + Then the record reads back to the module byte for byte, every row pins its axioms exactly, and the theorems elaborate, replay, and pass the audit + And every theorem's statement is definitionally the one the committed text certificate elaborated to + And a wrong lemma application, a changed statement, a forged program literal, and an added axiom are each refused diff --git a/model/ids.toml b/model/ids.toml index c0d2ecddb..8040a0621 100644 --- a/model/ids.toml +++ b/model/ids.toml @@ -1886,6 +1886,12 @@ level = "build" suite = "preservation" statement = "Certificate E of every production root is kernel-term data: a native core module of schema lexlean/core-module/2 that the compiler builds from the lowered program, the layout, and the recorded types of the preservation library's lemmas, every argument and universe level explicit, which the one core backend renders to the Lean that verification compiles and whose record is the authoring notation, reading back to the data byte for byte; its theorems state what §17.17 states for the composition, each row pins its axioms exactly and the audit module prints and checks them, a certificate held as data is audited row by row and never as text (no axiom row, no forbidden constant, imports within the environment), and a planted wrong lemma, a changed statement, a forged crate reference, or an added axiom is refused." +[[id]] +id = "SP-15" +level = "build" +suite = "preservation" +statement = "Certificate A of every production root is kernel-term data: a native core module of schema lexlean/core-module/2 that the compiler builds from the lowered program, the layout, the semantic source of the root's closure, and the recorded types of the preservation library's lemmas, every argument and universe level explicit, so that no Lean text template for certificate A remains in the production compiler; its program literal, encoders, width predicates, relation theorems, validators, and entry are rows of that module, which the one core backend renders to the Lean that verification compiles and whose record is the authoring notation, reading back to the data byte for byte; each theorem states, up to elaboration, the statement the text certificate stated, which an oracle over the committed corpora records, every row pins its axioms exactly, and a wrong lemma application, a changed statement, a forged program literal, or an added axiom is refused." + [[id]] id = "MD-01" level = "build" diff --git a/xtask/src/spec_links.rs b/xtask/src/spec_links.rs index 19ce9650b..1621c3685 100644 --- a/xtask/src/spec_links.rs +++ b/xtask/src/spec_links.rs @@ -61,8 +61,8 @@ fn parse_table(spec: &str) -> Result, Fail> { pub fn validate(root: &Path) -> Result<(), Fail> { let spec = std::fs::read_to_string(root.join("SPEC.md"))?; let rows = parse_table(&spec)?; - if rows.len() != 383 { - return Err(format!("RP-07: the §31 table has {} rows, not 383", rows.len()).into()); + if rows.len() != 384 { + return Err(format!("RP-07: the §31 table has {} rows, not 384", rows.len()).into()); } // The prose total under the table is part of the contract a reader // relies on; a row added without it would leave §31 contradicting itself. From 9949f67d6e5aa1d4701151130641ce7f67887a7f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 15:44:41 +0000 Subject: [PATCH 02/41] #89 stage 3: kernel terms by free variables, placeholders, and incremental lemma application Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- .../src/production/certificate/data.rs | 26 + crates/lexlean/src/production/kernel.rs | 6 + crates/lexlean/src/production/kernel/ext.rs | 814 ++++++++++++++++++ 3 files changed, 846 insertions(+) create mode 100644 crates/lexlean/src/production/kernel/ext.rs diff --git a/crates/lexlean/src/production/certificate/data.rs b/crates/lexlean/src/production/certificate/data.rs index 661d7ef03..a66b7cdee 100644 --- a/crates/lexlean/src/production/certificate/data.rs +++ b/crates/lexlean/src/production/certificate/data.rs @@ -2288,3 +2288,29 @@ impl Gen<'_> { Composer::new(self, module_a, module_b, name).module(root_module, root_name, fallible) } } + +#[cfg(test)] +mod dev { + use super::*; + + /// Print the recorded types of the lemmas named in `LEXLEAN_LEMMAS` + /// (comma separated), for the author of a generator. + #[test] + #[ignore = "a developer's lens on the native library"] + fn print_lemma_types() { + let names = std::env::var("LEXLEAN_LEMMAS").unwrap_or_default(); + let mut b = Builder::new(); + for name in names.split(',').filter(|name| !name.is_empty()) { + let (module, declaration) = preserve::native_declaration(name).expect("a declaration"); + let ty = b.import(module, declaration.r#type); + println!("{name} : {}\n", b.show(ty)); + if std::env::var("LEXLEAN_ROWS").is_ok() { + println!("{declaration:?}\n"); + if let Some(value) = declaration.value { + let value = b.import(module, value); + println!("{name} := {}\n", b.show(value)); + } + } + } + } +} diff --git a/crates/lexlean/src/production/kernel.rs b/crates/lexlean/src/production/kernel.rs index 000893dd7..34d88fe3d 100644 --- a/crates/lexlean/src/production/kernel.rs +++ b/crates/lexlean/src/production/kernel.rs @@ -20,6 +20,10 @@ use std::collections::{BTreeSet, HashMap}; use crate::ir::core::{CoreBinderInfo, CoreLevel, CoreModule, CoreNode}; +mod ext; + +pub use ext::{Given, Open}; + /// A node of the DAG under construction. pub type Term = usize; @@ -48,6 +52,8 @@ pub struct Builder { shared: HashMap, /// The library lemmas the terms built so far apply, by name. lemmas: BTreeSet, + /// The next placeholder number ([`ext`]). + fresh: usize, } /// The empty binder name, which the notation writes as `_`. diff --git a/crates/lexlean/src/production/kernel/ext.rs b/crates/lexlean/src/production/kernel/ext.rs new file mode 100644 index 000000000..724dd1e04 --- /dev/null +++ b/crates/lexlean/src/production/kernel/ext.rs @@ -0,0 +1,814 @@ +//! Locally nameless construction on the kernel builder (SPEC.md §17.17). +//! +//! A certificate is built inside binders (the parameters of a function, the +//! fields of a matched constructor), so the generator holds terms that mention +//! them before the binder exists. A *free variable* is a placeholder constant +//! no declaration can be named: [`Builder::abstract_over`] turns it into the +//! bound variable of the lambda or Π that closes it, and nothing else ever +//! creates a loose variable, so the instantiation of [`super`] needs no +//! lifting. +//! +//! A lemma of the preservation library is applied with its implicit and +//! instance binders as *metavariables*, placeholders of the same kind. They +//! are solved by writing the binder types the library records against the +//! types of the arguments given for the explicit binders (a syntactic +//! comparison that assigns a placeholder where the pattern has one and never +//! reduces, searches for an instance, or inserts a coercion), by the hints the +//! generator holds from the statement it is proving, or, for a reflexivity +//! argument, by the left side of the equation it states. Nothing here decides +//! whether a term is well typed: the kernel does, on the finished module, and +//! a placeholder left unsolved is refused by the generator by name. + +use std::collections::HashMap; + +use super::{Applied, Builder, Term}; +use crate::ir::core::{CoreBinderInfo, CoreNode}; + +const FREE: &str = "\u{1}free."; +const META: &str = "\u{1}meta."; + +/// An explicit argument of a lemma application. +#[derive(Debug, Clone, Copy)] +pub enum Given { + /// A term whose type the generator does not need. + Term(Term), + /// A term and its type, which solves the placeholders of the binder it + /// fills. + Proof(Term, Term), + /// `Eq.refl` at the equation the binder states. + Refl, +} + +impl Builder { + /// A fresh free variable. + pub fn free(&mut self) -> Term { + let id = self.fresh; + self.fresh += 1; + self.konst(&format!("{FREE}{id}")) + } + + fn meta(&mut self) -> (usize, Term) { + let id = self.fresh; + self.fresh += 1; + (id, self.konst(&format!("{META}{id}"))) + } + + fn meta_id(&self, term: Term) -> Option { + match &self.nodes[term] { + CoreNode::Const { n, .. } => n.strip_prefix(META).and_then(|id| id.parse().ok()), + _ => None, + } + } + + /// `term` with the free variable `free` bound by the binder around it. + pub fn abstract_over(&mut self, term: Term, free: Term) -> Term { + let mut memo = HashMap::new(); + self.abstract_memo(term, free, 0, &mut memo) + } + + fn abstract_memo( + &mut self, + term: Term, + free: Term, + depth: usize, + memo: &mut HashMap<(Term, usize), Term>, + ) -> Term { + if term == free { + return self.bvar(depth); + } + if let Some(&done) = memo.get(&(term, depth)) { + return done; + } + let out = match self.nodes[term].clone() { + CoreNode::BVar { .. } + | CoreNode::Sort { .. } + | CoreNode::Const { .. } + | CoreNode::Nat { .. } + | CoreNode::String { .. } => term, + CoreNode::App { f, x } => { + let f = self.abstract_memo(f, free, depth, memo); + let x = self.abstract_memo(x, free, depth, memo); + self.app(f, x) + } + CoreNode::Lambda { n, b, t, v } => { + let t = self.abstract_memo(t, free, depth, memo); + let v = self.abstract_memo(v, free, depth + 1, memo); + self.lam(&n, b, t, v) + } + CoreNode::Forall { n, b, t, v } => { + let t = self.abstract_memo(t, free, depth, memo); + let v = self.abstract_memo(v, free, depth + 1, memo); + self.pi(&n, b, t, v) + } + CoreNode::Let { n, t, v, body, d } => { + let t = self.abstract_memo(t, free, depth, memo); + let v = self.abstract_memo(v, free, depth, memo); + let body = self.abstract_memo(body, free, depth + 1, memo); + self.let_node(&n, t, v, body, d) + } + CoreNode::Mdata { m, x } => { + let x = self.abstract_memo(x, free, depth, memo); + self.mdata_node(m, x) + } + CoreNode::Projection { n, s, i, x } => { + let x = self.abstract_memo(x, free, depth, memo); + self.projection(&n, &s, i, x) + } + }; + memo.insert((term, depth), out); + out + } + + /// `fun (name : ty) => body` over the free variable `free` of `body`. + pub fn lam_over( + &mut self, + name: &str, + binder: CoreBinderInfo, + ty: Term, + free: Term, + body: Term, + ) -> Term { + let body = self.abstract_over(body, free); + self.lam(name, binder, ty, body) + } + + /// `(name : ty) -> body` over the free variable `free` of `body`. + pub fn pi_over( + &mut self, + name: &str, + binder: CoreBinderInfo, + ty: Term, + free: Term, + body: Term, + ) -> Term { + let body = self.abstract_over(body, free); + self.pi(name, binder, ty, body) + } + + /// `let name : ty := value; body` over the free variable `free` of `body`. + pub fn let_over(&mut self, name: &str, ty: Term, value: Term, free: Term, body: Term) -> Term { + let body = self.abstract_over(body, free); + self.let_node(name, ty, value, body, false) + } + + /// A `let` node whose body is already written over `#0`. + pub fn let_node(&mut self, name: &str, ty: Term, value: Term, body: Term, dep: bool) -> Term { + self.push(CoreNode::Let { + n: name.to_owned(), + t: ty, + v: value, + body, + d: dep, + }) + } + + fn mdata_node(&mut self, entries: Vec, x: Term) -> Term { + self.push(CoreNode::Mdata { m: entries, x }) + } + + /// A primitive projection. + pub fn projection(&mut self, name: &str, structure: &str, index: usize, x: Term) -> Term { + self.push(CoreNode::Projection { + n: name.to_owned(), + s: structure.to_owned(), + i: index, + x, + }) + } + + /// `term` with every constant named in `map` replaced by its term. + pub fn replace_consts(&mut self, term: Term, map: &HashMap) -> Term { + if map.is_empty() { + return term; + } + let mut memo = HashMap::new(); + self.replace_memo(term, map, &mut memo) + } + + fn replace_memo( + &mut self, + term: Term, + map: &HashMap, + memo: &mut HashMap, + ) -> Term { + if let Some(&done) = memo.get(&term) { + return done; + } + let out = match self.nodes[term].clone() { + CoreNode::Const { n, .. } => map.get(&n).copied().unwrap_or(term), + CoreNode::BVar { .. } + | CoreNode::Sort { .. } + | CoreNode::Nat { .. } + | CoreNode::String { .. } => term, + CoreNode::App { f, x } => { + let f = self.replace_memo(f, map, memo); + let x = self.replace_memo(x, map, memo); + self.app(f, x) + } + CoreNode::Lambda { n, b, t, v } => { + let t = self.replace_memo(t, map, memo); + let v = self.replace_memo(v, map, memo); + self.lam(&n, b, t, v) + } + CoreNode::Forall { n, b, t, v } => { + let t = self.replace_memo(t, map, memo); + let v = self.replace_memo(v, map, memo); + self.pi(&n, b, t, v) + } + CoreNode::Let { n, t, v, body, d } => { + let t = self.replace_memo(t, map, memo); + let v = self.replace_memo(v, map, memo); + let body = self.replace_memo(body, map, memo); + self.let_node(&n, t, v, body, d) + } + CoreNode::Mdata { m, x } => { + let x = self.replace_memo(x, map, memo); + self.mdata_node(m, x) + } + CoreNode::Projection { n, s, i, x } => { + let x = self.replace_memo(x, map, memo); + self.projection(&n, &s, i, x) + } + }; + memo.insert(term, out); + out + } + + /// `term` as a readable expression, for the generator's error messages. + #[must_use] + pub fn show(&self, term: Term) -> String { + self.show_at(term, &mut Vec::new(), 0) + } + + fn show_at(&self, term: Term, names: &mut Vec, depth: usize) -> String { + if depth > 40 { + return "…".to_owned(); + } + match &self.nodes[term] { + CoreNode::BVar { i } => names + .len() + .checked_sub(i + 1) + .and_then(|at| names.get(at).cloned()) + .unwrap_or_else(|| format!("#{i}")), + CoreNode::Sort { l } => format!("Sort({l:?})"), + CoreNode::Const { n, .. } => n.replace(FREE, "$").replace(META, "?"), + CoreNode::Nat { v } => v.clone(), + CoreNode::String { v } => format!( + "{:?}", + v.iter().filter_map(|c| char::from_u32(*c)).collect::() + ), + CoreNode::App { .. } => { + let (head, args) = self.spine(term); + let mut out = format!("({}", self.show_at(head, names, depth + 1)); + for arg in args { + out.push(' '); + out.push_str(&self.show_at(arg, names, depth + 1)); + } + out.push(')'); + out + } + CoreNode::Lambda { n, b, t, v } | CoreNode::Forall { n, b, t, v } => { + let keyword = if matches!(self.nodes[term], CoreNode::Lambda { .. }) { + "fun" + } else { + "∀" + }; + let domain = self.show_at(*t, names, depth + 1); + let label = if n.is_empty() { "_" } else { n.as_str() }; + names.push(label.to_owned()); + let body = self.show_at(*v, names, depth + 1); + names.pop(); + let (open, close) = match b { + CoreBinderInfo::Explicit => ("(", ")"), + CoreBinderInfo::Implicit => ("{", "}"), + CoreBinderInfo::StrictImplicit => ("⦃", "⦄"), + CoreBinderInfo::Instance => ("[", "]"), + }; + format!("({keyword} {open}{label} : {domain}{close}, {body})") + } + CoreNode::Let { n, t, v, body, .. } => { + let value = self.show_at(*v, names, depth + 1); + let ty = self.show_at(*t, names, depth + 1); + names.push(n.clone()); + let rest = self.show_at(*body, names, depth + 1); + names.pop(); + format!("(let {n} : {ty} := {value}; {rest})") + } + CoreNode::Mdata { x, .. } => self.show_at(*x, names, depth + 1), + CoreNode::Projection { n, x, .. } => { + format!("({}).{n}", self.show_at(*x, names, depth + 1)) + } + } + } + + /// Whether `term` mentions a free variable or a placeholder. + #[must_use] + pub fn has_placeholder(&self, term: Term) -> bool { + let mut seen = std::collections::HashSet::new(); + self.placeholder_walk(term, &mut seen) + } + + fn placeholder_walk(&self, term: Term, seen: &mut std::collections::HashSet) -> bool { + if !seen.insert(term) { + return false; + } + match &self.nodes[term] { + CoreNode::Const { n, .. } => n.starts_with(FREE) || n.starts_with(META), + CoreNode::BVar { .. } + | CoreNode::Sort { .. } + | CoreNode::Nat { .. } + | CoreNode::String { .. } => false, + CoreNode::App { f, x } => { + self.placeholder_walk(*f, seen) || self.placeholder_walk(*x, seen) + } + CoreNode::Lambda { t, v, .. } | CoreNode::Forall { t, v, .. } => { + self.placeholder_walk(*t, seen) || self.placeholder_walk(*v, seen) + } + CoreNode::Let { t, v, body, .. } => { + self.placeholder_walk(*t, seen) + || self.placeholder_walk(*v, seen) + || self.placeholder_walk(*body, seen) + } + CoreNode::Mdata { x, .. } | CoreNode::Projection { x, .. } => { + self.placeholder_walk(*x, seen) + } + } + } + + /// Assign the placeholders of `pattern` that `actual` has a term at, by + /// comparing the two node by node. A shape the two do not share is left + /// to the kernel. + fn solve(&self, pattern: Term, actual: Term, solved: &mut HashMap) { + if pattern == actual { + return; + } + if let Some(id) = self.meta_id(pattern) { + if !self.has_loose(actual, 0) { + solved.entry(id).or_insert(actual); + } + return; + } + match (&self.nodes[pattern], &self.nodes[actual]) { + (CoreNode::App { f, x }, CoreNode::App { f: g, x: y }) => { + self.solve(*f, *g, solved); + self.solve(*x, *y, solved); + } + ( + CoreNode::Lambda { t, v, .. } | CoreNode::Forall { t, v, .. }, + CoreNode::Lambda { t: u, v: w, .. } | CoreNode::Forall { t: u, v: w, .. }, + ) => { + self.solve(*t, *u, solved); + self.solve(*v, *w, solved); + } + ( + CoreNode::Let { t, v, body, .. }, + CoreNode::Let { + t: t2, + v: v2, + body: b2, + .. + }, + ) => { + self.solve(*t, *t2, solved); + self.solve(*v, *v2, solved); + self.solve(*body, *b2, solved); + } + (CoreNode::Projection { x, .. }, CoreNode::Projection { x: y, .. }) => { + self.solve(*x, *y, solved); + } + _ => {} + } + } + + /// `body` with `#0` replaced by `arg`, a function applied to the bound + /// variable reduced as the elaborator reduces it when it instantiates a + /// binder: the type of `conv_cond` states `P b` and the type of the + /// application states the predicate's body. + pub fn instantiate_beta(&mut self, body: Term, arg: Term) -> Term { + let mut memo = HashMap::new(); + self.beta_memo(body, arg, 0, &mut memo) + } + + fn beta_memo( + &mut self, + term: Term, + arg: Term, + depth: usize, + memo: &mut HashMap<(Term, usize), Term>, + ) -> Term { + if let Some(&done) = memo.get(&(term, depth)) { + return done; + } + let out = match self.nodes[term].clone() { + CoreNode::BVar { i } => { + if i == depth { + self.lift(arg, depth, 0) + } else if i > depth { + self.bvar(i - 1) + } else { + term + } + } + CoreNode::Sort { .. } + | CoreNode::Const { .. } + | CoreNode::Nat { .. } + | CoreNode::String { .. } => term, + CoreNode::App { f, x } => { + let f = self.beta_memo(f, arg, depth, memo); + let x = self.beta_memo(x, arg, depth, memo); + match self.nodes[f].clone() { + CoreNode::Lambda { v, .. } => self.instantiate_beta(v, x), + _ => self.app(f, x), + } + } + CoreNode::Lambda { n, b, t, v } => { + let t = self.beta_memo(t, arg, depth, memo); + let v = self.beta_memo(v, arg, depth + 1, memo); + self.lam(&n, b, t, v) + } + CoreNode::Forall { n, b, t, v } => { + let t = self.beta_memo(t, arg, depth, memo); + let v = self.beta_memo(v, arg, depth + 1, memo); + self.pi(&n, b, t, v) + } + CoreNode::Let { n, t, v, body, d } => { + let t = self.beta_memo(t, arg, depth, memo); + let v = self.beta_memo(v, arg, depth, memo); + let body = self.beta_memo(body, arg, depth + 1, memo); + self.let_node(&n, t, v, body, d) + } + CoreNode::Mdata { m, x } => { + let x = self.beta_memo(x, arg, depth, memo); + self.mdata_node(m, x) + } + CoreNode::Projection { n, s, i, x } => { + let x = self.beta_memo(x, arg, depth, memo); + self.projection(&n, &s, i, x) + } + }; + memo.insert((term, depth), out); + out + } + + /// `term` with every solved placeholder replaced, transitively. + fn resolve(&mut self, term: Term, solved: &HashMap) -> Term { + let map: HashMap = solved + .iter() + .map(|(id, value)| (format!("{META}{id}"), *value)) + .collect(); + let mut out = term; + for _ in 0..64 { + let mut memo = HashMap::new(); + let next = self.resolve_memo(out, &map, &mut memo); + if next == out { + return out; + } + out = next; + } + out + } + + /// Replacement of placeholders that reduces a solved function applied to + /// its arguments, as instantiating a binder does. + fn resolve_memo( + &mut self, + term: Term, + map: &HashMap, + memo: &mut HashMap, + ) -> Term { + if let Some(&done) = memo.get(&term) { + return done; + } + let out = match self.nodes[term].clone() { + CoreNode::Const { n, .. } => map.get(&n).copied().unwrap_or(term), + CoreNode::BVar { .. } + | CoreNode::Sort { .. } + | CoreNode::Nat { .. } + | CoreNode::String { .. } => term, + CoreNode::App { f, x } => { + let (head, _) = self.spine(f); + let replaced_head = self.meta_id(head).is_some() + || matches!(&self.nodes[head], CoreNode::Const { n, .. } if map.contains_key(n)); + let f2 = self.resolve_memo(f, map, memo); + let x2 = self.resolve_memo(x, map, memo); + match self.nodes[f2].clone() { + CoreNode::Lambda { v, .. } if replaced_head => self.instantiate_beta(v, x2), + _ => self.app(f2, x2), + } + } + CoreNode::Lambda { n, b, t, v } => { + let t = self.resolve_memo(t, map, memo); + let v = self.resolve_memo(v, map, memo); + self.lam(&n, b, t, v) + } + CoreNode::Forall { n, b, t, v } => { + let t = self.resolve_memo(t, map, memo); + let v = self.resolve_memo(v, map, memo); + self.pi(&n, b, t, v) + } + CoreNode::Let { n, t, v, body, d } => { + let t = self.resolve_memo(t, map, memo); + let v = self.resolve_memo(v, map, memo); + let body = self.resolve_memo(body, map, memo); + self.let_node(&n, t, v, body, d) + } + CoreNode::Mdata { m, x } => { + let x = self.resolve_memo(x, map, memo); + self.mdata_node(m, x) + } + CoreNode::Projection { n, s, i, x } => { + let x = self.resolve_memo(x, map, memo); + self.projection(&n, &s, i, x) + } + }; + memo.insert(term, out); + out + } + + /// Open a lemma for application: every binder of `ty` becomes a + /// placeholder, the hint of its name (when `hints` has one) is its + /// solution, and the caller supplies the explicit ones, in order, with + /// [`Open::give`] and reads what is already determined with + /// [`Open::domain`] and [`Open::value`]. + /// + /// # Errors + /// + /// None; the signature mirrors [`Open::finish`] for symmetry. + pub fn open_lemma( + &mut self, + name: &str, + head: Term, + ty: Term, + hints: &[(&str, Term)], + ) -> Result { + let mut open = Open { + name: name.to_owned(), + head, + binders: Vec::new(), + conclusion: ty, + solved: HashMap::new(), + next: 0, + }; + let mut ty = ty; + loop { + let (binder, info, domain, body) = match &self.nodes[ty] { + CoreNode::Forall { n, b, t, v } => (n.clone(), *b, *t, *v), + _ => break, + }; + let (id, placeholder) = self.meta(); + if let Some((_, hint)) = hints.iter().find(|(wanted, _)| *wanted == binder) { + open.solved.insert(id, *hint); + } + open.binders.push(OpenBinder { + name: binder, + explicit: info == CoreBinderInfo::Explicit, + domain, + id, + placeholder, + }); + ty = self.instantiate_beta(body, placeholder); + } + open.conclusion = ty; + Ok(open) + } + + /// `head`, whose type is `ty`, applied to `given` at its explicit binders. + /// An implicit or instance binder takes the hint of its name when `hints` + /// has one, else the term the types of the arguments determine. + /// + /// # Errors + /// + /// More arguments than explicit binders, a reflexivity argument at a + /// binder that is not an equation, or a placeholder no argument, hint, or + /// reflexivity determined. + pub fn apply_lemma( + &mut self, + name: &str, + head: Term, + ty: Term, + hints: &[(&str, Term)], + given: &[Given], + ) -> Result { + let mut open = self.open_lemma(name, head, ty, hints)?; + for argument in given { + open.give(self, *argument)?; + } + open.finish(self) + } +} + +/// One binder of a lemma being applied. +#[derive(Debug, Clone)] +struct OpenBinder { + name: String, + explicit: bool, + /// Its type, over the placeholders of the earlier binders. + domain: Term, + id: usize, + placeholder: Term, +} + +/// A lemma being applied: its binders as placeholders, with what is solved +/// so far. +#[derive(Debug, Clone)] +pub struct Open { + name: String, + head: Term, + binders: Vec, + conclusion: Term, + solved: HashMap, + /// The next explicit binder [`Open::give`] fills. + next: usize, +} + +impl Open { + fn explicit_at(&self, position: usize) -> Option { + self.binders + .iter() + .enumerate() + .filter(|(_, binder)| binder.explicit) + .nth(position) + .map(|(index, _)| index) + } + + /// Solve the placeholders of the lemma's conclusion that `expected`, the + /// type its application is wanted at, has a term at. + pub fn expect(&mut self, b: &Builder, expected: Term) { + b.solve(self.conclusion, expected, &mut self.solved); + } + + /// Solve the placeholders of a binder's type against the type of the + /// argument it takes, without giving the argument. + pub fn solve_domain(&mut self, b: &Builder, binder: &str, actual: Term) { + if let Some(found) = self.binders.iter().find(|candidate| candidate.name == binder) { + let domain = found.domain; + b.solve(domain, actual, &mut self.solved); + } + } + + /// The type of the next explicit binder, with what is solved written in. + pub fn domain(&self, b: &mut Builder) -> Term { + let at = self.explicit_at(self.next).map_or_else( + || { + self.binders + .iter() + .find(|binder| binder.explicit) + .map_or(self.conclusion, |binder| binder.domain) + }, + |index| self.binders[index].domain, + ); + b.resolve(at, &self.solved) + } + + /// The solution of the binder named `name`, when it has one. + pub fn value(&self, b: &mut Builder, name: &str) -> Option { + let binder = self.binders.iter().find(|binder| binder.name == name)?; + let value = *self.solved.get(&binder.id)?; + Some(b.resolve(value, &self.solved)) + } + + /// Give the next explicit binder its argument. + /// + /// # Errors + /// + /// A reflexivity argument at a binder that is not an equation, or more + /// arguments than explicit binders. + pub fn give(&mut self, b: &mut Builder, argument: Given) -> Result<(), String> { + let Some(index) = self.explicit_at(self.next) else { + return Err(format!( + "`{}`: more arguments than explicit binders ({})", + self.name, self.next + )); + }; + let domain = self.binders[index].domain; + let id = self.binders[index].id; + let position = self.next; + self.next += 1; + let value = match argument { + Given::Term(value) => value, + Given::Proof(value, actual) => { + b.solve(domain, actual, &mut self.solved); + value + } + Given::Refl => { + let domain = b.resolve(domain, &self.solved); + let (eq, parts) = b.spine(domain); + let levels = match &b.nodes[eq] { + CoreNode::Const { n, u } if n == "Eq" && parts.len() == 3 => u.clone(), + _ => { + return Err(format!( + "`{}`: argument {position} is a reflexivity proof at {}, which is not an equation", + self.name, + b.show(domain) + )); + } + }; + if let Some(solution) = b.meta_id(parts[2]) { + self.solved.entry(solution).or_insert(parts[1]); + } + if let Some(solution) = b.meta_id(parts[1]) { + self.solved.entry(solution).or_insert(parts[2]); + } + let refl = b.konst_at("Eq.refl", levels); + let sides = b.resolve(parts[1], &self.solved); + let carrier = b.resolve(parts[0], &self.solved); + b.apps(refl, &[carrier, sides]) + } + }; + self.solved.insert(id, value); + Ok(()) + } + + /// The application, once every explicit binder has its argument. + /// + /// # Errors + /// + /// A binder no argument, hint, or expected type determined, by name. + pub fn finish(self, b: &mut Builder) -> Result { + let mut term = self.head; + for binder in &self.binders { + let value = match self.solved.get(&binder.id) { + Some(value) => *value, + None => binder.placeholder, + }; + term = b.app(term, value); + } + let term = b.resolve(term, &self.solved); + let ty = b.resolve(self.conclusion, &self.solved); + if b.has_placeholder(term) || b.has_placeholder(ty) { + let open: Vec<&str> = self + .binders + .iter() + .filter(|binder| !self.solved.contains_key(&binder.id)) + .map(|binder| binder.name.as_str()) + .collect(); + return Err(format!( + "`{}`: the binders {open:?} are determined by no argument, hint, or expected type", + self.name + )); + } + Ok(Applied { term, ty }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn a_free_variable_becomes_the_bound_variable_of_its_lambda() { + let mut b = Builder::new(); + let nat = b.konst("Nat"); + let x = b.free(); + let f = b.konst("f"); + let body = b.apps(f, &[x, x]); + let lam = b.lam_over("x", CoreBinderInfo::Explicit, nat, x, body); + let bound = b.bvar(0); + let applied = b.apps(f, &[bound, bound]); + assert!(!b.has_placeholder(lam)); + match b.node(lam) { + CoreNode::Lambda { v, .. } => assert_eq!(*v, applied), + other => panic!("not a lambda: {other:?}"), + } + } + + #[test] + fn implicit_binders_are_solved_from_the_types_of_the_arguments() { + // lemma : {a : Nat} -> (h : P a) -> Q a + let mut b = Builder::new(); + let nat = b.konst("Nat"); + let p = b.konst("P"); + let q = b.konst("Q"); + let a0 = b.bvar(0); + let pa = b.app(p, a0); + let a1 = b.bvar(1); + let qa = b.app(q, a1); + let inner = b.pi("h", CoreBinderInfo::Explicit, pa, qa); + let ty = b.pi("a", CoreBinderInfo::Implicit, nat, inner); + let lemma = b.konst("lemma"); + let five = b.nat(5); + let proof = b.konst("proof"); + let p5 = b.app(p, five); + let applied = b + .apply_lemma("lemma", lemma, ty, &[], &[Given::Proof(proof, p5)]) + .expect("a is determined by the argument's type"); + let q5 = b.app(q, five); + assert_eq!(applied.ty, q5); + let (_, args) = b.spine(applied.term); + assert_eq!(args, vec![five, proof]); + } + + #[test] + fn an_undetermined_implicit_binder_is_named() { + let mut b = Builder::new(); + let nat = b.konst("Nat"); + let prop = b.prop(); + let lifted = b.lift(prop, 1, 0); + let ty = b.pi("a", CoreBinderInfo::Implicit, nat, lifted); + let lemma = b.konst("lemma"); + let error = b + .apply_lemma("lemma", lemma, ty, &[], &[]) + .expect_err("nothing determines a"); + assert!(error.contains("[\"a\"]"), "{error}"); + } +} From 80b96cdb8d1a262014371ba1b1929097a3f04f69 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 16:13:30 +0000 Subject: [PATCH 03/41] #89 stage 3: certificate A as kernel-term data, first root (program literal, width predicate, relation, denote, root) accepted by the kernel Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- crates/conformance/src/preservation.rs | 91 ++ crates/lexlean/src/production/certificate.rs | 23 +- .../src/production/certificate/data_a.rs | 557 +++++++++ .../production/certificate/data_a/boundary.rs | 21 + .../production/certificate/data_a/calls.rs | 630 ++++++++++ .../src/production/certificate/data_a/lean.rs | 858 ++++++++++++++ .../production/certificate/data_a/matching.rs | 311 +++++ .../production/certificate/data_a/prims.rs | 368 ++++++ .../production/certificate/data_a/prove.rs | 953 ++++++++++++++++ .../src/production/certificate/data_a/rows.rs | 595 ++++++++++ .../src/production/certificate/data_a/src.rs | 1013 +++++++++++++++++ .../certificate/data_a/templates.rs | 33 + crates/lexlean/src/production/kernel/ext.rs | 253 +++- 13 files changed, 5667 insertions(+), 39 deletions(-) create mode 100644 crates/lexlean/src/production/certificate/data_a.rs create mode 100644 crates/lexlean/src/production/certificate/data_a/boundary.rs create mode 100644 crates/lexlean/src/production/certificate/data_a/calls.rs create mode 100644 crates/lexlean/src/production/certificate/data_a/lean.rs create mode 100644 crates/lexlean/src/production/certificate/data_a/matching.rs create mode 100644 crates/lexlean/src/production/certificate/data_a/prims.rs create mode 100644 crates/lexlean/src/production/certificate/data_a/prove.rs create mode 100644 crates/lexlean/src/production/certificate/data_a/rows.rs create mode 100644 crates/lexlean/src/production/certificate/data_a/src.rs create mode 100644 crates/lexlean/src/production/certificate/data_a/templates.rs diff --git a/crates/conformance/src/preservation.rs b/crates/conformance/src/preservation.rs index 0d7776bca..56d77e4c7 100644 --- a/crates/conformance/src/preservation.rs +++ b/crates/conformance/src/preservation.rs @@ -2841,3 +2841,94 @@ pub fn rustc_differential(project: &P, certified: &[Certified], example: &str) - entered, } } + +#[cfg(test)] +mod dev { + use super::*; + + /// Build certificate A of the roots of an example (`LEXLEAN_EXAMPLE`, + /// default `production`; `LEXLEAN_ROOTS`, comma-separated indices, default + /// all), print what the generator says of each, then compile and replay + /// the ones it built. A developer's lens, not a gate. + #[test] + #[ignore = "a developer's lens on certificate A"] + fn dev_certificate_a() { + let example = std::env::var("LEXLEAN_EXAMPLE").unwrap_or_else(|_| "production".to_owned()); + let only: Option> = std::env::var("LEXLEAN_ROOTS").ok().map(|list| { + list.split(',') + .filter_map(|item| item.trim().parse().ok()) + .collect() + }); + let project = P::copy_example(&example); + let checked = support::checked_project(&project); + let modules = linked_modules(&checked); + let hex32: String = checked.semantic_id.to_hex()[..32].to_owned(); + let limits = support::limits(&project); + let mut built = Vec::new(); + for (index, root) in roots(&checked) + .expect("the eligibility reports") + .iter() + .enumerate() + { + if only.as_ref().is_some_and(|list| !list.contains(&index)) { + continue; + } + let lowered = lower_root(&modules, &root.module, &root.name, root.report, &limits) + .expect("lowering"); + let module = format!("LexLeanPreserve.C{hex32}.R{index}"); + match certificate( + &modules, + &root.module, + &root.name, + root.report, + &lowered, + &module, + limits.max_file_bytes, + ) { + Ok(certificate) => { + eprintln!( + "R{index} {}: built, {} bytes of Lean", + root.report.root, + certificate.text.len() + ); + built.push(certificate); + } + Err(diagnostic) => { + eprintln!("R{index} {}: {}", root.report.root, diagnostic.message); + } + } + } + if built.is_empty() { + return; + } + if let Ok(dump) = std::env::var("LEXLEAN_DUMP") { + for certificate in &built { + let path = std::path::Path::new(&dump).join(format!("{}.lean", certificate.module)); + std::fs::write(&path, &certificate.text).expect("dump"); + } + } + let rendered = support::rendered(&project); + let user: Vec<(String, String)> = rendered + .modules + .iter() + .map(|module| (module.lean_module.clone(), module.lean_text.clone())) + .collect(); + let staged = workspace(&user, &built).expect("the workspace stages"); + let scratch = tempfile::Builder::new() + .prefix("lexlean-dev-a-") + .tempdir() + .expect("tempdir"); + let result = check(&staged, scratch.path()); + match result.failure { + Some((module, output)) => { + let head: String = output.lines().take(60).collect::>().join("\n"); + eprintln!("`{module}` was rejected:\n{head}"); + panic!("rejected"); + } + None => { + audit(&result.audit_output, &built).expect("the axiom audit"); + eprintln!("{} certificates A: compiled, replayed, audited", built.len()); + } + } + } +} diff --git a/crates/lexlean/src/production/certificate.rs b/crates/lexlean/src/production/certificate.rs index 0152ce35d..1aaa29c8e 100644 --- a/crates/lexlean/src/production/certificate.rs +++ b/crates/lexlean/src/production/certificate.rs @@ -36,6 +36,7 @@ use std::collections::{BTreeMap, BTreeSet}; mod data; +mod data_a; use super::eligibility::BUILTIN_OWNERS; use super::lean_list::{array, list, list_by}; @@ -451,7 +452,7 @@ pub fn certificate_e( let text = crate::backend::core::lean_text_legacy(&data, module) .map_err(|diagnostic| internal(diagnostic.message))?; generator.within(text.len()).map_err(failure)?; - let source = record_source(module, &data).map_err(internal)?; + let source = record_source(module, &data, "E").map_err(internal)?; Ok(Certificate { module: module.to_owned(), theorem: format!("{module}.root"), @@ -465,7 +466,11 @@ pub fn certificate_e( /// The record of a certificate held as data: the whole source of the module /// in the authoring notation (SPEC.md §17.21.8), which reads back to the data. -fn record_source(module: &str, data: &crate::ir::core::CoreModule) -> Result { +fn record_source( + module: &str, + data: &crate::ir::core::CoreModule, + kind: &str, +) -> Result { let name = module.rsplit('.').next().unwrap_or(module); let value = serde_json::to_value(data).map_err(|error| error.to_string())?; crate::sdk::assemble( @@ -473,7 +478,7 @@ fn record_source(module: &str, data: &crate::ir::core::CoreModule) -> Result Result { let mut generator = generator(modules, lowered, max_bytes); generator.nest().map_err(failure)?; - let text = generator - .module(root_module, root_name, report, module) + let data = generator + .module_data(root_module, root_name, report, module) .map_err(failure)?; - let text = from_the_root(&text).map_err(internal)?; + let text = crate::backend::core::lean_text_legacy(&data, module) + .map_err(|diagnostic| internal(diagnostic.message))?; generator.within(text.len()).map_err(failure)?; + let source = record_source(module, &data, "A").map_err(internal)?; let mut imports: Vec = modules .values() .map(|linked| linked.lean_module.to_owned()) @@ -593,8 +600,8 @@ pub fn certificate( denote: format!("{module}.denote"), text, imports, - data: None, - source: None, + data: Some(data), + source: Some(source), }) } diff --git a/crates/lexlean/src/production/certificate/data_a.rs b/crates/lexlean/src/production/certificate/data_a.rs new file mode 100644 index 000000000..d05c0b228 --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a.rs @@ -0,0 +1,557 @@ +//! Certificate A as kernel-term data (SPEC.md §17.17, `SP-15`). +//! +//! Certificate A of one lowered root states that the lowered program, run from +//! function 0 on the encoded arguments, converges, overflows exactly when the +//! width predicate is false, and otherwise returns the encoding of the root's +//! own Lean denotation. Here the module is built as a native core module: the +//! program literal `__prog`, the encoders `__enc_k`, one width predicate +//! `__fits_k` and one relation theorem `__rel_k` per function of the closure, +//! the boundary validators and entry, then `denote` and `root`. Each is a +//! kernel term whose every argument and universe level is written; a lemma of +//! the preservation library is applied by instantiating its recorded Π-type +//! ([`super::super::kernel`]), the arguments the generator holds from the +//! lowered program (the operator, the operand expressions, the environment) +//! given as hints, and the rest read off the types of the arguments. +//! +//! The derivation follows the lowered program node by node: the proof of a +//! source term is built together with the expression the lowering produced +//! for it, so that the program's own operator or variable number is what a +//! lemma is applied at. A lowering that realizes a construct other than as its +//! rule says leaves a lemma whose statement the kernel finds different from the +//! program's, and the kernel refuses the module (§17.17.6). + +use std::collections::{BTreeMap, BTreeSet, HashMap}; + +use super::super::kernel::{Builder, Given, Open, Term}; +use super::super::preserve; +use super::{components, CLocal, Ctx, Gen, Subject, SYNTAX}; +use crate::calculus::Expr; +use crate::ir::core::{ + CoreAxiomPolicy, CoreBinderInfo, CoreDeclKind, CoreDeclaration, CoreModule, CoreReducibility, + CoreTransparency, CORE_SPEC_2, +}; +use crate::ir::semantic::SemanticType; + +mod boundary; +mod calls; +mod lean; +mod matching; +mod prims; +mod prove; +mod rows; +mod src; +mod templates; + +pub(super) const LIB: &str = "LexLeanPreservation"; +pub(super) const SEM: &str = "LexLeanTarget.TargetSemantics"; + +/// The kind of a row of the module. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum RowKind { + Theorem, + /// A definition; `compiled` is whether the code generator runs over it, + /// which the differential evaluator needs of `denote` and what it calls. + Definition { compiled: bool }, +} + +/// One row of the module. +#[derive(Debug, Clone)] +pub(super) struct Row { + pub(super) name: String, + pub(super) ty: Term, + pub(super) value: Term, + pub(super) kind: RowKind, + pub(super) axioms: BTreeSet, +} + +/// A proof of a program expression's relation, with its type +/// `Conv p env e (Rel fits value)`. +#[derive(Debug, Clone, Copy)] +pub(super) struct Pf { + pub(super) proof: Term, + pub(super) ty: Term, +} + +/// The terms of a function-typed local's companion: the width predicate, +/// function index, captured values, and relation hypothesis of the closure it +/// is bound to. +#[derive(Debug, Clone, Copy)] +pub(super) struct CompTerms { + /// The type of `relation`, over the placeholders of the callee's own + /// parameters. + pub(super) relation_ty: Term, + pub(super) fits: Term, + pub(super) index: Term, + pub(super) captures: Term, + pub(super) relation: Term, +} + +/// A local of a scope: the free variable standing for its value and, for a +/// function-typed one, its companions. +#[derive(Debug, Clone, Copy)] +pub(super) struct LocalTerm { + pub(super) value: Term, + pub(super) companion: Option, +} + +/// Where a term is proved: the source context, the terms of its locals, and +/// the evaluation environment the program runs it in. +#[derive(Clone)] +pub(super) struct Scope { + pub(super) ctx: Ctx, + pub(super) terms: Vec, + pub(super) env: Term, +} + +impl Scope { + pub(super) fn lookup(&self, name: &str) -> Result<(&CLocal, LocalTerm), String> { + self.ctx + .locals + .iter() + .enumerate() + .rev() + .find(|(_, local)| local.local.name == name) + .map(|(position, local)| (local, self.terms[position])) + .ok_or_else(|| format!("local `{name}` is unbound")) + } +} + +/// The builder of one certificate A module. +pub(super) struct Writer<'g, 'a> { + pub(super) gen: &'g mut Gen<'a>, + pub(super) b: Builder, + pub(super) name: String, + lemmas: HashMap, + pub(super) rows: Vec, + row_axioms: BTreeMap>, + /// The declared exact axioms of each declaration of the source, by its + /// Lean name. + declared: HashMap>, + /// The generated modules, which each carry the runtime. + runtime_modules: BTreeSet, + /// `module.__prog`. + pub(super) prog: Term, + /// The rendering of each expression of the program, by address. + exprs: HashMap<*const Expr, Term>, + /// The signature of each function whose relation has been stated. + pub(super) sigs: HashMap, +} + +impl<'g, 'a> Writer<'g, 'a> { + pub(super) fn new(gen: &'g mut Gen<'a>, name: &str) -> Self { + let mut b = Builder::new(); + let prog = b.konst(&format!("{name}.__prog")); + let mut declared = HashMap::new(); + let mut runtime_modules = BTreeSet::new(); + for linked in gen.source.modules.values() { + runtime_modules.insert(linked.lean_module.to_owned()); + for declaration in linked.semantic.lowered_declarations() { + let mut axioms = declaration.axioms().to_vec(); + axioms.sort(); + declared.insert( + format!("{}.{}", linked.lean_module, declaration.name()), + axioms, + ); + } + } + Self { + gen, + b, + name: name.to_owned(), + lemmas: HashMap::new(), + rows: Vec::new(), + row_axioms: BTreeMap::new(), + declared, + runtime_modules, + prog, + exprs: HashMap::new(), + sigs: HashMap::new(), + } + } + + // --- Names ------------------------------------------------------------ + + pub(super) fn c(&mut self, name: &str) -> Term { + self.b.konst(name) + } + + pub(super) fn ts(&mut self, name: &str) -> Term { + self.b.konst(&format!("{SYNTAX}.{name}")) + } + + pub(super) fn lp(&mut self, name: &str) -> Term { + self.b.konst(&format!("{LIB}.{name}")) + } + + pub(super) fn sem(&mut self, name: &str) -> Term { + self.b.konst(&format!("{SEM}.{name}")) + } + + /// A row of this module, by its short name. + pub(super) fn own(&mut self, name: &str) -> Term { + let full = format!("{}.{name}", self.name); + self.b.konst(&full) + } + + // --- Lemmas ----------------------------------------------------------- + + /// The head and type of a library declaration, its type imported from the + /// native library data. + pub(super) fn lemma(&mut self, name: &str) -> Result<(Term, Term), String> { + let full = format!("{LIB}.{name}"); + if let Some(&found) = self.lemmas.get(&full) { + return Ok(found); + } + let (module, declaration) = preserve::native_declaration(&full)?; + if !declaration.levels.is_empty() { + return Err(format!( + "`{full}` has universe parameters, which a certificate does not instantiate" + )); + } + let ty = self.b.import(module, declaration.r#type); + let head = self.b.konst(&full); + self.lemmas.insert(full.clone(), (head, ty)); + Ok((head, ty)) + } + + /// A library lemma opened for application with the given hints. + pub(super) fn open(&mut self, name: &str, hints: &[(&str, Term)]) -> Result { + let (head, ty) = self.lemma(name)?; + self.b.open_lemma(name, head, ty, hints) + } + + /// A library lemma applied to arguments at once. + pub(super) fn apply( + &mut self, + name: &str, + hints: &[(&str, Term)], + given: &[Given], + ) -> Result { + let mut open = self.open(name, hints)?; + for argument in given { + open.give(&mut self.b, *argument)?; + } + let applied = open.finish(&mut self.b)?; + Ok(Pf { + proof: applied.term, + ty: applied.ty, + }) + } + + /// The axioms a constant brings, as `#print axioms` reports them: the + /// registry's for a library declaration, the row's own for a row of this + /// module, the declared exact set for a declaration of the source, and for + /// the runtime every generated module carries, the set its text depends + /// on. A constant of Lean's own libraries brings none the certificate + /// does not already pin through the lemma that mentions it. + fn constant_axioms(&self, name: &str) -> Result, String> { + if let Some(axioms) = self.row_axioms.get(name) { + return Ok(axioms.iter().cloned().collect()); + } + if name.starts_with(&format!("{LIB}.")) { + return preserve::library_axioms(name); + } + if let Some(axioms) = self.declared.get(name) { + return Ok(axioms.clone()); + } + // The eliminators, constructors, and projections of a declared type. + let mut owner = name; + while let Some((parent, _)) = owner.rsplit_once('.') { + owner = parent; + if let Some(axioms) = self.declared.get(owner) { + return Ok(axioms.clone()); + } + } + if let Some((module, rest)) = name.split_once(".LexLeanRuntime.") { + if self.runtime_modules.contains(module) { + return Ok(runtime_axioms(rest)); + } + } + Ok(Vec::new()) + } + + /// The axioms of the row whose type and value are `roots`: those of every + /// constant they mention. + fn row_axiom_set(&self, roots: &[Term]) -> Result, String> { + let mut out = BTreeSet::new(); + let mut seen = std::collections::HashSet::new(); + let mut names = BTreeSet::new(); + let mut stack: Vec = roots.to_vec(); + while let Some(term) = stack.pop() { + if !seen.insert(term) { + continue; + } + match self.b.node(term) { + crate::ir::core::CoreNode::Const { n, .. } => { + names.insert(n.clone()); + } + crate::ir::core::CoreNode::App { f, x } => { + stack.push(*f); + stack.push(*x); + } + crate::ir::core::CoreNode::Lambda { t, v, .. } + | crate::ir::core::CoreNode::Forall { t, v, .. } => { + stack.push(*t); + stack.push(*v); + } + crate::ir::core::CoreNode::Let { t, v, body, .. } => { + stack.push(*t); + stack.push(*v); + stack.push(*body); + } + crate::ir::core::CoreNode::Mdata { x, .. } + | crate::ir::core::CoreNode::Projection { x, .. } => stack.push(*x), + crate::ir::core::CoreNode::BVar { .. } + | crate::ir::core::CoreNode::Sort { .. } + | crate::ir::core::CoreNode::Nat { .. } + | crate::ir::core::CoreNode::String { .. } => {} + } + } + for name in names { + out.extend(self.constant_axioms(&name)?); + } + Ok(out) + } + + /// Add a row, pinning the axioms of what it applied and referenced. + pub(super) fn push_row( + &mut self, + name: &str, + kind: RowKind, + ty: Term, + value: Term, + ) -> Result<(), String> { + let full = format!("{}.{name}", self.name); + if self.b.has_placeholder(ty) || self.b.has_placeholder(value) { + return Err(format!( + "the row `{full}` of certificate A has a free variable or placeholder left" + )); + } + if self.b.has_loose(ty, 0) || self.b.has_loose(value, 0) { + return Err(format!( + "the row `{full}` of certificate A has a loose bound variable" + )); + } + let axioms = self.row_axiom_set(&[ty, value])?; + self.row_axioms.insert(full.clone(), axioms.clone()); + self.rows.push(Row { + name: full, + ty, + value, + kind, + axioms, + }); + Ok(()) + } + + /// The module. + pub(super) fn finish(self, imports: Vec) -> Result { + let heights = self.heights(); + let roots: Vec = self + .rows + .iter() + .flat_map(|row| [row.ty, row.value]) + .collect(); + let (nodes, mapped) = self.b.compact(&roots); + let declarations: Vec = self + .rows + .iter() + .enumerate() + .map(|(position, row)| { + declaration( + row, + heights[&row.name], + mapped[position * 2], + mapped[position * 2 + 1], + ) + }) + .collect(); + let module = CoreModule { + spec: CORE_SPEC_2.to_owned(), + imports, + nodes, + proof_nodes: Vec::new(), + declarations, + }; + module.validate(crate::LANGUAGE_1_2)?; + Ok(module) + } + + /// The reducibility height of each row: one above the rows it names, so + /// the kernel unfolds a row before the rows it is stated with. + fn heights(&self) -> HashMap { + let mut heights = HashMap::new(); + for row in &self.rows { + let mut height = 0u32; + let mut seen = std::collections::HashSet::new(); + let mut stack = vec![row.value, row.ty]; + while let Some(term) = stack.pop() { + if !seen.insert(term) { + continue; + } + match self.b.node(term) { + crate::ir::core::CoreNode::Const { n, .. } => { + if let Some(&below) = heights.get(n) { + height = height.max(below + 1); + } + } + crate::ir::core::CoreNode::App { f, x } => { + stack.push(*f); + stack.push(*x); + } + crate::ir::core::CoreNode::Lambda { t, v, .. } + | crate::ir::core::CoreNode::Forall { t, v, .. } => { + stack.push(*t); + stack.push(*v); + } + crate::ir::core::CoreNode::Let { t, v, body, .. } => { + stack.push(*t); + stack.push(*v); + stack.push(*body); + } + crate::ir::core::CoreNode::Mdata { x, .. } + | crate::ir::core::CoreNode::Projection { x, .. } => stack.push(*x), + crate::ir::core::CoreNode::BVar { .. } + | crate::ir::core::CoreNode::Sort { .. } + | crate::ir::core::CoreNode::Nat { .. } + | crate::ir::core::CoreNode::String { .. } => {} + } + } + heights.insert(row.name.clone(), height); + } + heights + } +} + +fn declaration(row: &Row, height: u32, ty: Term, value: Term) -> CoreDeclaration { + let (kind, reducibility, compiled) = match row.kind { + RowKind::Theorem => (CoreDeclKind::Theorem, None, false), + RowKind::Definition { compiled } => ( + CoreDeclKind::Definition, + Some(CoreReducibility::Regular(height + 1)), + compiled, + ), + }; + CoreDeclaration { + all: Some(vec![row.name.clone()]), + name: row.name.clone(), + levels: Vec::new(), + kind, + reducibility, + transparency: CoreTransparency::Semireducible, + policy: CoreAxiomPolicy::Exact(row.axioms.iter().cloned().collect()), + r#type: ty, + value: Some(value), + inductive: None, + class: false, + instance: None, + generated: false, + safety: None, + compiled, + tags: Vec::new(), + no_confusion: None, + matcher: None, + projection: None, + simp: None, + eqn_info: None, + inline: None, + sparse: None, + grind: None, + } +} + +impl Gen<'_> { + /// Certificate A of the root as a native core module (SPEC.md §17.17). + pub(super) fn module_data( + &mut self, + root_module: &str, + root_name: &str, + report: &super::RootReport, + name: &str, + ) -> Result { + let graph = self.collect()?; + self.module = name.to_owned(); + let mut imports: Vec = super::LIBRARY_MODULES + .iter() + .map(|module| (*module).to_owned()) + .collect(); + let mut users: Vec<&str> = self + .source + .modules + .values() + .map(|linked| linked.lean_module) + .collect(); + users.sort_unstable(); + imports.extend(users.into_iter().map(str::to_owned)); + let mut writer = Writer::new(self, name); + writer.write(root_module, root_name, report, &graph)?; + writer.finish(imports) + } +} + +impl Writer<'_, '_> { + /// The rows in order: the program, the encoders, the relation of each + /// component, then `denote`, `root`, and the boundary. + fn write( + &mut self, + root_module: &str, + root_name: &str, + report: &super::RootReport, + graph: &BTreeMap>, + ) -> Result<(), String> { + let program = self.program_value()?; + let program_ty = self.ts("Program"); + self.push_row( + "__prog", + RowKind::Definition { compiled: false }, + program_ty, + program, + )?; + self.encoders()?; + self.gen.found.clear(); + for component in components(graph) { + self.component(&component, graph)?; + } + self.root(root_module, root_name, report) + } +} + +impl<'a> Gen<'a> { + pub(super) fn subject_of(&self, index: u64) -> Result, String> { + self.subjects + .get(&index) + .cloned() + .ok_or_else(|| format!("function {index} has no subject")) + } +} + +/// Binder information of an explicit binder, for the lambdas and Π-types of +/// rows. +pub(super) const EXPLICIT: CoreBinderInfo = CoreBinderInfo::Explicit; + +/// The axioms of a declaration of the runtime every generated module carries +/// (`backend::semantic`), by its name below `LexLeanRuntime`, as `#print +/// axioms` reports them. The audit module prints the axioms of every row, so +/// a runtime that changes disagrees with this table at the first certificate. +fn runtime_axioms(name: &str) -> Vec { + let all = || { + ["Classical.choice", "Quot.sound", "propext"] + .iter() + .map(|axiom| (*axiom).to_owned()) + .collect() + }; + let some = |axioms: &[&str]| axioms.iter().map(|axiom| (*axiom).to_owned()).collect(); + match name { + "compareBytes" | "join" => some(&["propext"]), + "instDecimalInt" + | "instDecimalOfFixedOfToString" + | "instLengthableString" + | "splitExact" + | "utf8Decode" => all(), + "instFixedInt8" | "instFixedInt16" | "instFixedInt32" | "instFixedInt64" + | "instFixedUInt8" | "instFixedUInt16" | "instFixedUInt32" | "instFixedUInt64" => { + some(&["Quot.sound", "propext"]) + } + _ => Vec::new(), + } +} diff --git a/crates/lexlean/src/production/certificate/data_a/boundary.rs b/crates/lexlean/src/production/certificate/data_a/boundary.rs new file mode 100644 index 000000000..88ec7ac2f --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/boundary.rs @@ -0,0 +1,21 @@ +//! The boundary validators and the entry (SPEC.md §17.17). + +use super::rows::Sig; +use super::Writer; +use crate::ir::semantic::SemanticType; + +impl Writer<'_, '_> { + /// The validators and entry of a root with invariant-carrying parameters. + pub(super) fn boundary( + &mut self, + sig: &Sig, + parameters: &[SemanticType], + ) -> Result<(), String> { + let _ = sig; + self.gen.verify_boundary(parameters)?; + if self.gen.checks().is_empty() && self.gen.entry_of().is_none() { + return Ok(()); + } + Err("boundary validators".to_owned()) + } +} diff --git a/crates/lexlean/src/production/certificate/data_a/calls.rs b/crates/lexlean/src/production/certificate/data_a/calls.rs new file mode 100644 index 000000000..d60d3520f --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/calls.rs @@ -0,0 +1,630 @@ +//! Calls, function values, and applications (SPEC.md §17.17.4): the relation +//! of a callee applied to the arguments' denotations, and a function-typed +//! argument carried with its companions, the width predicate, closure index, +//! captured values, and relation of the closure it is bound to. + +use super::prove::Passed; +use super::{CompTerms, Pf, Scope, Writer, EXPLICIT}; +use crate::calculus::Expr; +use crate::ir::semantic::{SemanticTerm, SemanticType}; +use crate::production::kernel::{Given, Term}; +use crate::production::source::Source; + +/// A function-typed argument with its companions. +struct FunctionArgument { + value: Term, + fits: Term, + index: Term, + captures: Term, + relation: Term, + proof: Pf, + /// The function the program numbered it (unused for a parameter). + number: u64, +} + +/// The parameter and result types of a closed function type. +fn function_parts(ty: &SemanticType) -> Option<(Vec, SemanticType)> { + match ty { + SemanticType::Function { parameters, result } => { + Some((parameters.clone(), result.as_ref().clone())) + } + SemanticType::Type + | SemanticType::Parameter { name: _ } + | SemanticType::Nat + | SemanticType::Bool + | SemanticType::Prop + | SemanticType::Unit + | SemanticType::Int + | SemanticType::Int8 + | SemanticType::Int16 + | SemanticType::Int32 + | SemanticType::Int64 + | SemanticType::UInt8 + | SemanticType::UInt16 + | SemanticType::UInt32 + | SemanticType::UInt64 + | SemanticType::String + | SemanticType::Bytes + | SemanticType::Ordering + | SemanticType::Option { value: _ } + | SemanticType::Result { ok: _, error: _ } + | SemanticType::List { element: _ } + | SemanticType::Named { + member: _, + arguments: _, + } + | SemanticType::Product { left: _, right: _ } + | SemanticType::Map { key: _, value: _ } + | SemanticType::Set { element: _ } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure => None, + } +} + +impl<'a> Writer<'_, 'a> { + /// The relation theorem `__rel_index` applied to `args`, with the type it + /// states there: the statement the function's signature fixes, written + /// at the arguments. + pub(super) fn call_relation(&mut self, index: u64, args: &[Term]) -> Result { + let (params, statement) = { + let sig = self + .sigs + .get(&index) + .ok_or_else(|| format!("function {index} has no signature yet"))?; + (sig.rel_params.clone(), sig.statement) + }; + if params.len() != args.len() { + return Err(format!( + "function {index}'s relation takes {} arguments, given {}", + params.len(), + args.len() + )); + } + let head = self.own(&format!("__rel_{index}")); + let proof = self.b.apps(head, args); + let ty = self.b.substitute(statement, ¶ms, args); + Ok(Pf { proof, ty }) + } + + /// The arguments a callee's relation and width predicate take for one + /// source argument: the value, and for a function value its companions. + pub(super) fn argument( + &mut self, + argument: &SemanticTerm, + expr: &'a Expr, + scope: &Scope, + ) -> Result { + match self.function_argument(argument, expr, scope)? { + Some(function) => Ok(Passed { + fits: vec![function.value, function.fits], + relation: vec![ + function.value, + function.fits, + function.index, + function.captures, + function.relation, + ], + proof: function.proof, + }), + None => { + let proof = self.prove(argument, expr, scope)?; + let value = self.src(argument, scope)?; + Ok(Passed { + fits: vec![value], + relation: vec![value], + proof, + }) + } + } + } + + /// `fun (p0 : T0) … => body`, over the parameters of a function type, + /// where `body` is built from their free variables. + fn over_parameters( + &mut self, + parameters: &[SemanticType], + body: impl FnOnce(&mut Self, &[Term]) -> Result, + ) -> Result { + let mut frees = Vec::new(); + let mut types = Vec::new(); + for parameter in parameters { + frees.push(self.b.free()); + types.push(self.lean_ty(parameter)?); + } + let mut out = body(self, &frees)?; + for position in (0..frees.len()).rev() { + out = self + .b + .lam_over(&format!("__p{position}"), EXPLICIT, types[position], frees[position], out); + } + Ok(out) + } + + /// The companions of a function value at the lambda or function the + /// program numbered `index`, closing over `captures`. + fn closure_companions( + &mut self, + index: u64, + parameters: &[SemanticType], + fits_captures: Vec, + relation_captures: Vec, + ) -> Result<(Term, Term), String> { + let fits_head = self.own(&format!("__fits_{index}")); + let rel_head = self.own(&format!("__rel_{index}")); + let fits = self.over_parameters(parameters, |writer, frees| { + let mut all = fits_captures.clone(); + all.extend_from_slice(frees); + Ok(writer.b.apps(fits_head, &all)) + })?; + let relation = self.over_parameters(parameters, |writer, frees| { + let mut all = relation_captures.clone(); + all.extend_from_slice(frees); + Ok(writer.b.apps(rel_head, &all)) + })?; + Ok((fits, relation)) + } + + /// A function-typed argument's value and companions; `None` for a + /// first-order argument. + fn function_argument( + &mut self, + argument: &SemanticTerm, + expr: &'a Expr, + scope: &Scope, + ) -> Result, String> { + let ty = self.infer(argument, scope)?; + let Some((parameters, _)) = function_parts(&ty) else { + return Ok(None); + }; + let (p, env) = (self.prog, scope.env); + match argument { + SemanticTerm::Var { name } => { + let number = match expr { + Expr::Var { name } => *name, + _ => return Err(format!("the lowering of `{name}` is not a variable")), + }; + let (_, terms) = scope.lookup(name)?; + let companion = terms + .companion + .ok_or_else(|| format!("function-typed `{name}` has no companion"))?; + let closure = self.ts("Value.closure"); + let value = self.b.apps(closure, &[companion.index, companion.captures]); + let x = self.nat_u(number); + let proof = self.apply( + "conv_var", + &[("p", p), ("env", env), ("x", x), ("v", value)], + &[Given::Refl], + )?; + Ok(Some(FunctionArgument { + value: terms.value, + fits: companion.fits, + index: companion.index, + captures: companion.captures, + relation: companion.relation, + proof, + number: 0, + })) + } + SemanticTerm::Lambda { + parameters: _, + captures, + body: _, + } => { + let (function, capture_exprs) = match expr { + Expr::Closure { function, captures } => (*function, captures), + _ => return Err("the lowering of a lambda is not a closure".to_owned()), + }; + let index = self.gen.next_lambda(scope.ctx.function)?; + if index != function { + return Err(format!( + "a lambda numbered {index} lowered to the closure of {function}" + )); + } + let mut fits_captures = Vec::new(); + let mut relation_captures = Vec::new(); + let mut encoded = Vec::new(); + let mut proofs = Vec::new(); + for (capture, capture_expr) in captures.iter().zip(capture_exprs) { + let (local, terms) = scope.lookup(capture)?; + let local = local.clone(); + match terms.companion { + Some(companion) => { + fits_captures.push(terms.value); + fits_captures.push(companion.fits); + relation_captures.extend([ + terms.value, + companion.fits, + companion.index, + companion.captures, + companion.relation, + ]); + } + None => { + fits_captures.push(terms.value); + relation_captures.push(terms.value); + } + } + encoded.push(self.encoded_local(&local, terms)?); + let number = match capture_expr { + Expr::Var { name } => *name, + _ => return Err("a capture lowered to other than a variable".to_owned()), + }; + let value = *encoded.last().ok_or("a capture")?; + let x = self.nat_u(number); + proofs.push(self.apply( + "conv_var", + &[("p", p), ("env", env), ("x", x), ("v", value)], + &[Given::Refl], + )?); + } + let list = self.operands(env, &proofs)?; + let fi = self.nat_u(index); + let proof = self.apply( + "conv_closure", + &[("p", p), ("env", env), ("fi", fi)], + &[Given::Proof(list.proof, list.ty)], + )?; + let (fits, relation) = + self.closure_companions(index, ¶meters, fits_captures, relation_captures)?; + let value_ty = self.value_ty(); + let captures_term = self.b.list(value_ty, &encoded); + let value = self.src(argument, scope)?; + Ok(Some(FunctionArgument { + value, + fits, + index: fi, + captures: captures_term, + relation, + proof, + number: index, + })) + } + SemanticTerm::FunctionRef { + function, + type_arguments, + } => { + let module = Source::module_of(function, &scope.ctx.site); + let closed: Vec = type_arguments + .iter() + .map(|argument| self.gen.source.close(argument, &scope.ctx.site)) + .collect(); + let index = self + .gen + .instance_index(&module, &function.name, &closed)?; + let list = self.operands(env, &[])?; + let fi = self.nat_u(index); + let proof = self.apply( + "conv_closure", + &[("p", p), ("env", env), ("fi", fi)], + &[Given::Proof(list.proof, list.ty)], + )?; + let (fits, relation) = + self.closure_companions(index, ¶meters, Vec::new(), Vec::new())?; + let value_ty = self.value_ty(); + let captures_term = self.b.list(value_ty, &[]); + let value = self.src(argument, scope)?; + Ok(Some(FunctionArgument { + value, + fits, + index: fi, + captures: captures_term, + relation, + proof, + number: index, + })) + } + SemanticTerm::Nat { value: _ } + | SemanticTerm::Integer { + representation: _, + value: _, + } + | SemanticTerm::String { value: _ } + | SemanticTerm::Bytes { hex: _ } + | SemanticTerm::Primitive { + operation: _, + arguments: _, + result: _, + } + | SemanticTerm::Bool { value: _ } + | SemanticTerm::Unit + | SemanticTerm::Nil { element: _ } + | SemanticTerm::Cons { head: _, tail: _ } + | SemanticTerm::Record { + r#type: _, + type_arguments: _, + fields: _, + } + | SemanticTerm::Constructor { + constructor: _, + type_arguments: _, + arguments: _, + } + | SemanticTerm::InstanceValue { + class: _, + arguments: _, + resolved: _, + } + | SemanticTerm::Project { value: _, field: _ } + | SemanticTerm::Call { + function: _, + type_arguments: _, + arguments: _, + } + | SemanticTerm::If { + condition: _, + then_value: _, + else_value: _, + } + | SemanticTerm::Match { + scrutinee: _, + branches: _, + } + | SemanticTerm::Eq { left: _, right: _ } + | SemanticTerm::Le { left: _, right: _ } + | SemanticTerm::Lt { left: _, right: _ } + | SemanticTerm::Add { left: _, right: _ } + | SemanticTerm::Beq { left: _, right: _ } + | SemanticTerm::Ble { left: _, right: _ } + | SemanticTerm::Blt { left: _, right: _ } + | SemanticTerm::And { left: _, right: _ } + | SemanticTerm::PropAnd { left: _, right: _ } + | SemanticTerm::Or { left: _, right: _ } + | SemanticTerm::Not { value: _ } + | SemanticTerm::Implies { + premise: _, + conclusion: _, + } + | SemanticTerm::Iff { left: _, right: _ } + | SemanticTerm::Forall { binder: _, body: _ } + | SemanticTerm::Let { + binder: _, + value: _, + body: _, + } + | SemanticTerm::Pair { left: _, right: _ } + | SemanticTerm::First { value: _ } + | SemanticTerm::Second { value: _ } + | SemanticTerm::Apply { + function: _, + arguments: _, + } + | SemanticTerm::MapLiteral { + key: _, + value: _, + entries: _, + } + | SemanticTerm::SetLiteral { + element: _, + elements: _, + } + | SemanticTerm::GraphLiteral { + node: _, + nodes: _, + edges: _, + } + | SemanticTerm::CheckedApply { + model: _, + type_arguments: _, + arguments: _, + checks: _, + } => Err(format!( + "a function value `{}` is neither a parameter, a lambda, nor a function reference", + super::super::super::eligibility::term_key(argument) + )), + } + } + + /// The proof of applying a function value to arguments. + pub(super) fn application( + &mut self, + function: &SemanticTerm, + arguments: &[SemanticTerm], + expr: &'a Expr, + scope: &Scope, + ) -> Result { + let (target_expr, operand_exprs) = match expr { + Expr::Apply { target, operands } => (target.as_ref(), operands.as_slice()), + _ => return Err("the lowering of an application is not an apply".to_owned()), + }; + let (p, env) = (self.prog, scope.env); + let mut srcs = Vec::new(); + for argument in arguments { + srcs.push(self.src(argument, scope)?); + } + let ty = self.infer(function, scope)?; + let (parameters, _) = function_parts(&ty).ok_or("an applied non-function")?; + let (target, relation) = match function { + SemanticTerm::Var { name } => { + let number = match target_expr { + Expr::Var { name } => *name, + _ => return Err(format!("the lowering of `{name}` is not a variable")), + }; + let (_, terms) = scope.lookup(name)?; + let companion = terms + .companion + .ok_or_else(|| format!("applied `{name}` has no companion"))?; + let closure = self.ts("Value.closure"); + let value = self.b.apps(closure, &[companion.index, companion.captures]); + let x = self.nat_u(number); + let target = self.apply( + "conv_var", + &[("p", p), ("env", env), ("x", x), ("v", value)], + &[Given::Refl], + )?; + let relation = self.relation_applied(companion, &srcs)?; + (target, relation) + } + SemanticTerm::Lambda { .. } | SemanticTerm::FunctionRef { .. } => { + let passed = self + .function_argument(function, target_expr, scope)? + .ok_or("a lambda is not a function value")?; + let callee = passed.number; + let _ = parameters; + let mut args = Vec::new(); + args.extend(self.captured_relation_args(function, scope)?); + args.extend(srcs.iter().copied()); + let relation = self.call_relation(callee, &args)?; + (passed.proof, relation) + } + SemanticTerm::Nat { value: _ } + | SemanticTerm::Integer { + representation: _, + value: _, + } + | SemanticTerm::String { value: _ } + | SemanticTerm::Bytes { hex: _ } + | SemanticTerm::Primitive { + operation: _, + arguments: _, + result: _, + } + | SemanticTerm::Bool { value: _ } + | SemanticTerm::Unit + | SemanticTerm::Nil { element: _ } + | SemanticTerm::Cons { head: _, tail: _ } + | SemanticTerm::Record { + r#type: _, + type_arguments: _, + fields: _, + } + | SemanticTerm::Constructor { + constructor: _, + type_arguments: _, + arguments: _, + } + | SemanticTerm::InstanceValue { + class: _, + arguments: _, + resolved: _, + } + | SemanticTerm::Project { value: _, field: _ } + | SemanticTerm::Call { + function: _, + type_arguments: _, + arguments: _, + } + | SemanticTerm::If { + condition: _, + then_value: _, + else_value: _, + } + | SemanticTerm::Match { + scrutinee: _, + branches: _, + } + | SemanticTerm::Eq { left: _, right: _ } + | SemanticTerm::Le { left: _, right: _ } + | SemanticTerm::Lt { left: _, right: _ } + | SemanticTerm::Add { left: _, right: _ } + | SemanticTerm::Beq { left: _, right: _ } + | SemanticTerm::Ble { left: _, right: _ } + | SemanticTerm::Blt { left: _, right: _ } + | SemanticTerm::And { left: _, right: _ } + | SemanticTerm::PropAnd { left: _, right: _ } + | SemanticTerm::Or { left: _, right: _ } + | SemanticTerm::Not { value: _ } + | SemanticTerm::Implies { + premise: _, + conclusion: _, + } + | SemanticTerm::Iff { left: _, right: _ } + | SemanticTerm::Forall { binder: _, body: _ } + | SemanticTerm::Let { + binder: _, + value: _, + body: _, + } + | SemanticTerm::Pair { left: _, right: _ } + | SemanticTerm::First { value: _ } + | SemanticTerm::Second { value: _ } + | SemanticTerm::Apply { + function: _, + arguments: _, + } + | SemanticTerm::MapLiteral { + key: _, + value: _, + entries: _, + } + | SemanticTerm::SetLiteral { + element: _, + elements: _, + } + | SemanticTerm::GraphLiteral { + node: _, + nodes: _, + edges: _, + } + | SemanticTerm::CheckedApply { + model: _, + type_arguments: _, + arguments: _, + checks: _, + } => { + return Err(format!( + "the applied `{}` is neither a parameter, a lambda, nor a function reference", + super::super::super::eligibility::term_key(function) + )); + } + }; + let mut proofs = Vec::new(); + for (argument, child) in arguments.iter().zip(operand_exprs) { + proofs.push(self.prove(argument, child, scope)?); + } + let list = self.operands(env, &proofs)?; + self.apply( + "conv_apply", + &[("p", p), ("env", env)], + &[ + Given::Proof(target.proof, target.ty), + Given::Proof(list.proof, list.ty), + Given::Proof(relation.proof, relation.ty), + ], + ) + } + + /// A function-typed local's relation hypothesis applied to arguments, + /// with the type it states there. + fn relation_applied(&mut self, companion: CompTerms, args: &[Term]) -> Result { + let mut term = companion.relation; + let mut ty = companion.relation_ty; + for arg in args { + let (_, body) = match self.b.node(ty) { + crate::ir::core::CoreNode::Forall { t, v, .. } => (*t, *v), + _ => return Err("a relation hypothesis applied past its binders".to_owned()), + }; + term = self.b.app(term, *arg); + ty = self.b.instantiate_beta(body, *arg); + } + Ok(Pf { proof: term, ty }) + } + + /// The captured arguments a lambda's relation takes before its + /// parameters. + fn captured_relation_args( + &mut self, + function: &SemanticTerm, + scope: &Scope, + ) -> Result, String> { + match function { + SemanticTerm::Lambda { captures, .. } => { + let mut out = Vec::new(); + for capture in captures { + let (_, terms) = scope.lookup(capture)?; + match terms.companion { + Some(companion) => out.extend([ + terms.value, + companion.fits, + companion.index, + companion.captures, + companion.relation, + ]), + None => out.push(terms.value), + } + } + Ok(out) + } + _ => Ok(Vec::new()), + } + } +} diff --git a/crates/lexlean/src/production/certificate/data_a/lean.rs b/crates/lexlean/src/production/certificate/data_a/lean.rs new file mode 100644 index 000000000..5b210714a --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/lean.rs @@ -0,0 +1,858 @@ +//! The Lean terms certificate A states, as kernel terms: types, encoders, the +//! target program's literals, and the source's denotation (`src`), each the +//! term the pinned Lean elaborates the text of §17.17 to. + +use super::super::NAT_BOUND; +use super::Writer; +use crate::calculus::{ + Arm, Expr, Function, IntKind, OrderingValue, Prim, Program, Shape, Ty, Value, +}; +use crate::ir::semantic::{MemberRef, SemanticType}; +use crate::production::kernel::Term; + +impl Writer<'_, '_> { + // --- Numbers and Booleans --------------------------------------------- + + /// `n : Nat` as the elaborator writes a numeral. + pub(super) fn nat_lit(&mut self, n: &str) -> Term { + let raw = self.b.nat_text(n); + let nat = self.b.konst("Nat"); + let of_nat = self.b.konst0("OfNat.ofNat"); + let inst = self.b.konst("instOfNatNat"); + let inst = self.b.app(inst, raw); + self.b.apps(of_nat, &[nat, raw, inst]) + } + + pub(super) fn nat_u(&mut self, n: u64) -> Term { + self.nat_lit(&n.to_string()) + } + + pub(super) fn tru(&mut self) -> Term { + self.b.konst("Bool.true") + } + + pub(super) fn fls(&mut self) -> Term { + self.b.konst("Bool.false") + } + + pub(super) fn boolean(&mut self, value: bool) -> Term { + if value { + self.tru() + } else { + self.fls() + } + } + + /// `a && b`. + pub(super) fn and(&mut self, a: Term, b: Term) -> Term { + let and = self.b.konst("Bool.and"); + self.b.apps(and, &[a, b]) + } + + /// `!a`. + pub(super) fn not(&mut self, a: Term) -> Term { + let not = self.b.konst("Bool.not"); + self.b.app(not, a) + } + + /// `if c then t else e` over a Boolean, at type `ty`. + pub(super) fn ite(&mut self, ty: Term, c: Term, t: Term, e: Term) -> Term { + let bool = self.b.konst("Bool"); + let tru = self.tru(); + let eq = self.b.konst1("Eq"); + let prop = self.b.apps(eq, &[bool, c, tru]); + let decide = self.b.konst("instDecidableEqBool"); + let decide = self.b.apps(decide, &[c, tru]); + let ite = self.b.konst1("ite"); + self.b.apps(ite, &[ty, prop, decide, t, e]) + } + + /// `a = b` at `ty` (a Prop), where `ty : Type`. + pub(super) fn eq(&mut self, ty: Term, a: Term, b: Term) -> Term { + let eq = self.b.konst1("Eq"); + self.b.apps(eq, &[ty, a, b]) + } + + pub(super) fn nat_add(&mut self, a: Term, b: Term) -> Term { + let nat = self.b.konst("Nat"); + let add = self.b.konst_at( + "HAdd.hAdd", + vec![ + crate::ir::core::CoreLevel::Zero, + crate::ir::core::CoreLevel::Zero, + crate::ir::core::CoreLevel::Zero, + ], + ); + let inst = self.b.konst0("instHAdd"); + let add_nat = self.b.konst("instAddNat"); + let inst = self.b.apps(inst, &[nat, add_nat]); + self.b.apps(add, &[nat, nat, nat, inst, a, b]) + } + + /// `Nat.blt a b`. + pub(super) fn nat_blt(&mut self, a: Term, b: Term) -> Term { + let blt = self.b.konst("Nat.blt"); + self.b.apps(blt, &[a, b]) + } + + /// The bound of a 64-bit natural, as the width predicates state it. + pub(super) fn nat_bound(&mut self) -> Term { + self.nat_lit(NAT_BOUND) + } + + /// `Nat.blt n 2^64`. + pub(super) fn below_bound(&mut self, n: Term) -> Term { + let bound = self.nat_bound(); + self.nat_blt(n, bound) + } + + // --- Types ------------------------------------------------------------ + + /// The Lean type of a closed source type. + pub(super) fn lean_ty(&mut self, ty: &SemanticType) -> Result { + Ok(match ty { + SemanticType::Nat => self.b.konst("Nat"), + SemanticType::Bool => self.b.konst("Bool"), + SemanticType::Unit => self.b.konst("Unit"), + SemanticType::Int => self.b.konst("Int"), + SemanticType::Int8 => self.b.konst("Int8"), + SemanticType::Int16 => self.b.konst("Int16"), + SemanticType::Int32 => self.b.konst("Int32"), + SemanticType::Int64 => self.b.konst("Int64"), + SemanticType::UInt8 => self.b.konst("UInt8"), + SemanticType::UInt16 => self.b.konst("UInt16"), + SemanticType::UInt32 => self.b.konst("UInt32"), + SemanticType::UInt64 => self.b.konst("UInt64"), + SemanticType::String => self.b.konst("String"), + SemanticType::Bytes => self.b.konst("ByteArray"), + SemanticType::Ordering => self.b.konst("Ordering"), + SemanticType::ContractViolation | SemanticType::ReasoningFailure => { + let bool = self.b.konst("Bool"); + self.b.pair_ty(bool, bool) + } + SemanticType::Option { value } => { + let inner = self.lean_ty(value)?; + let option = self.b.konst0("Option"); + self.b.app(option, inner) + } + SemanticType::Result { ok, error } => { + let error = self.lean_ty(error)?; + let ok = self.lean_ty(ok)?; + let except = self.b.konst00("Except"); + self.b.apps(except, &[error, ok]) + } + SemanticType::List { element } | SemanticType::Set { element } => { + let element = self.lean_ty(element)?; + self.b.list_ty(element) + } + SemanticType::Product { left, right } => { + let left = self.lean_ty(left)?; + let right = self.lean_ty(right)?; + self.b.pair_ty(left, right) + } + SemanticType::Map { key, value } => { + let key = self.lean_ty(key)?; + let value = self.lean_ty(value)?; + let entry = self.b.pair_ty(key, value); + self.b.list_ty(entry) + } + SemanticType::Function { parameters, result } => { + let mut out = self.lean_ty(result)?; + for parameter in parameters.iter().rev() { + let domain = self.lean_ty(parameter)?; + out = self.b.arrow(domain, out); + } + out + } + SemanticType::Named { member, arguments } => { + let head = self.document_name(member)?; + let head = self.b.konst(&head); + let mut args = Vec::new(); + for argument in arguments { + args.push(self.lean_ty(argument)?); + } + self.b.apps(head, &args) + } + SemanticType::Type | SemanticType::Prop | SemanticType::Parameter { name: _ } => { + return Err(format!( + "`{}` has no runtime encoding", + self.gen.source.type_text(ty) + )); + } + }) + } + + /// The Lean name of a document type. + pub(super) fn document_name(&self, member: &MemberRef) -> Result { + let module = member.module.clone().ok_or("an unanchored document type")?; + Ok(format!( + "{}.{}", + self.gen.source.lean_module(&module)?, + member.name + )) + } + + /// `Value`. + pub(super) fn value_ty(&mut self) -> Term { + self.ts("Value") + } + + // --- Encoders --------------------------------------------------------- + + /// The encoder of a closed first-order type into target values. + pub(super) fn enc(&mut self, ty: &SemanticType) -> Result { + match self.gen.nested.get(&self.gen.source.type_text(ty)) { + Some(nested) => { + let (index, kind) = (nested.index, nested.ty.clone()); + self.nested_enc(index, &kind) + } + None => self.plain_enc(ty), + } + } + + fn value_ctor(&mut self, name: &str) -> Term { + self.ts(&format!("Value.{name}")) + } + + fn plain_enc(&mut self, ty: &SemanticType) -> Result { + Ok(match ty { + SemanticType::Nat => self.value_ctor("nat"), + SemanticType::Bool => self.value_ctor("bool"), + SemanticType::Int => self.value_ctor("int"), + SemanticType::Int8 => self.value_ctor("i8"), + SemanticType::Int16 => self.value_ctor("i16"), + SemanticType::Int32 => self.value_ctor("i32"), + SemanticType::Int64 => self.value_ctor("i64"), + SemanticType::UInt8 => self.value_ctor("u8"), + SemanticType::UInt16 => self.value_ctor("u16"), + SemanticType::UInt32 => self.value_ctor("u32"), + SemanticType::UInt64 => self.value_ctor("u64"), + SemanticType::String => self.value_ctor("string"), + SemanticType::Bytes => self.value_ctor("bytes"), + SemanticType::Unit => self.lp("encUnit"), + SemanticType::Ordering => self.lp("encOrdering"), + SemanticType::ContractViolation | SemanticType::ReasoningFailure => { + let bool = self.b.konst("Bool"); + let value = self.value_ctor("bool"); + let pair = self.lp("encPair"); + self.b.apps(pair, &[bool, bool, value, value]) + } + SemanticType::Option { value } => { + let alpha = self.lean_ty(value)?; + let inner = self.enc(value)?; + let enc = self.lp("encOption"); + self.b.apps(enc, &[alpha, inner]) + } + SemanticType::Result { ok, error } => { + let epsilon = self.lean_ty(error)?; + let alpha = self.lean_ty(ok)?; + let ee = self.enc(error)?; + let ea = self.enc(ok)?; + let enc = self.lp("encExcept"); + self.b.apps(enc, &[epsilon, alpha, ee, ea]) + } + SemanticType::List { element } | SemanticType::Set { element } => { + let alpha = self.lean_ty(element)?; + let inner = self.enc(element)?; + let enc = self.lp("encList"); + self.b.apps(enc, &[alpha, inner]) + } + SemanticType::Product { left, right } => { + let alpha = self.lean_ty(left)?; + let beta = self.lean_ty(right)?; + let ea = self.enc(left)?; + let eb = self.enc(right)?; + let enc = self.lp("encPair"); + self.b.apps(enc, &[alpha, beta, ea, eb]) + } + SemanticType::Map { key, value } => { + let alpha = self.lean_ty(key)?; + let beta = self.lean_ty(value)?; + let ea = self.enc(key)?; + let eb = self.enc(value)?; + let pair = self.lp("encPair"); + let pair = self.b.apps(pair, &[alpha, beta, ea, eb]); + let entry = self.b.pair_ty(alpha, beta); + let enc = self.lp("encList"); + self.b.apps(enc, &[entry, pair]) + } + SemanticType::Named { + member: _, + arguments: _, + } => { + let text = self.gen.source.type_text(ty); + let index = self + .gen + .adts + .get(&text) + .ok_or_else(|| format!("`{text}` has no ADT in the layout"))?; + let name = format!("__enc_{index}"); + self.own(&name) + } + SemanticType::Function { + parameters: _, + result: _, + } + | SemanticType::Type + | SemanticType::Prop + | SemanticType::Parameter { name: _ } => { + return Err(format!( + "`{}` has no value encoding", + self.gen.source.type_text(ty) + )); + } + }) + } + + /// The encoder of a type nested in a recursive group: its bundle's `enc`. + fn nested_enc(&mut self, index: u64, ty: &SemanticType) -> Result { + match ty { + SemanticType::List { element: _ } + | SemanticType::Set { element: _ } + | SemanticType::Map { key: _, value: _ } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure => { + let bundle = self.own(&format!("__L_{index}")); + let enc = self.lp("ListEnc.enc"); + let alpha = self.nested_element(ty)?; + Ok(self.b.apps(enc, &[alpha, bundle])) + } + SemanticType::Option { value } => { + let bundle = self.own(&format!("__O_{index}")); + let enc = self.lp("OptEnc.enc"); + let alpha = self.lean_ty(value)?; + Ok(self.b.apps(enc, &[alpha, bundle])) + } + SemanticType::Result { ok: _, error: _ } + | SemanticType::Product { left: _, right: _ } + | SemanticType::Named { + member: _, + arguments: _, + } + | SemanticType::Function { + parameters: _, + result: _, + } + | SemanticType::Type + | SemanticType::Prop + | SemanticType::Parameter { name: _ } + | SemanticType::Nat + | SemanticType::Bool + | SemanticType::Unit + | SemanticType::Int + | SemanticType::Int8 + | SemanticType::Int16 + | SemanticType::Int32 + | SemanticType::Int64 + | SemanticType::UInt8 + | SemanticType::UInt16 + | SemanticType::UInt32 + | SemanticType::UInt64 + | SemanticType::String + | SemanticType::Bytes + | SemanticType::Ordering => Ok(self.own(&format!("__aux_{index}"))), + } + } + + /// The element type a list-like nested container holds. + pub(super) fn nested_element(&mut self, ty: &SemanticType) -> Result { + let element = super::super::element_of(ty)?; + self.lean_ty(&element) + } + + // --- The target program ----------------------------------------------- + + fn int_kind(&mut self, kind: IntKind) -> Term { + self.ts(&format!("IntKind.{}", kind.name())) + } + + pub(super) fn ty_term(&mut self, ty: &Ty) -> Term { + match ty { + Ty::Unit => self.ts("Ty.unit"), + Ty::Bool => self.ts("Ty.bool"), + Ty::Nat => self.ts("Ty.nat"), + Ty::Int => self.ts("Ty.int"), + Ty::Fixed { width } => { + let fixed = self.ts("Ty.fixed"); + let kind = self.int_kind(*width); + self.b.app(fixed, kind) + } + Ty::String => self.ts("Ty.string"), + Ty::Bytes => self.ts("Ty.bytes"), + Ty::Ordering => self.ts("Ty.ordering"), + Ty::Option { value } => { + let inner = self.ty_term(value); + let head = self.ts("Ty.option"); + self.b.app(head, inner) + } + Ty::Result { ok, error } => { + let ok = self.ty_term(ok); + let error = self.ty_term(error); + let head = self.ts("Ty.result"); + self.b.apps(head, &[ok, error]) + } + Ty::List { element } => { + let inner = self.ty_term(element); + let head = self.ts("Ty.list"); + self.b.app(head, inner) + } + Ty::Pair { left, right } => { + let left = self.ty_term(left); + let right = self.ty_term(right); + let head = self.ts("Ty.pair"); + self.b.apps(head, &[left, right]) + } + Ty::Adt { index } => { + let index = self.nat_u(*index); + let head = self.ts("Ty.adt"); + self.b.app(head, index) + } + Ty::Fn { parameters, result } => { + let ty = self.ts("Ty"); + let items: Vec = parameters.iter().map(|p| self.ty_term(p)).collect(); + let parameters = self.b.list(ty, &items); + let result = self.ty_term(result); + let head = self.ts("Ty.fn"); + self.b.apps(head, &[parameters, result]) + } + } + } + + /// A numeral of a fixed-width or mathematical integer type. + pub(super) fn int_literal(&mut self, type_name: &str, text: &str) -> Term { + let (negative, digits) = match text.strip_prefix('-') { + Some(rest) => (true, rest), + None => (false, text), + }; + let ty = self.b.konst(type_name); + let raw = self.b.nat_text(digits); + let of_nat = self.b.konst0("OfNat.ofNat"); + let instance = if type_name == "Int" { + self.b.konst("instOfNat") + } else { + self.b.konst(&format!("{type_name}.instOfNat")) + }; + let instance = self.b.app(instance, raw); + let positive = self.b.apps(of_nat, &[ty, raw, instance]); + if !negative { + return positive; + } + let neg = self.b.konst0("Neg.neg"); + let instance = if type_name == "Int" { + self.b.konst("Int.instNegInt") + } else { + self.b.konst(&format!("{type_name}.instNeg")) + }; + self.b.apps(neg, &[ty, ty, instance, positive]) + } + + pub(super) fn bytes_term(&mut self, hex: &str) -> Result { + let digits = hex.as_bytes(); + if !digits.len().is_multiple_of(2) { + return Err(format!("byte literal `{hex}` has odd length")); + } + let mut items = Vec::new(); + for pair in digits.chunks(2) { + let text = std::str::from_utf8(pair).map_err(|_| "a non-ASCII byte literal")?; + let value = u8::from_str_radix(text, 16) + .map_err(|_| format!("byte literal `{hex}` is not hexadecimal"))?; + items.push(self.int_literal("UInt8", &value.to_string())); + } + let byte = self.b.konst("UInt8"); + let list = self.b.list(byte, &items); + let to_array = self.b.konst0("List.toArray"); + let array = self.b.apps(to_array, &[byte, list]); + let mk = self.b.konst("ByteArray.mk"); + Ok(self.b.app(mk, array)) + } + + pub(super) fn value_term(&mut self, value: &Value) -> Result { + Ok(match value { + Value::Unit => self.ts("Value.unit"), + Value::Bool { value } => { + let head = self.ts("Value.bool"); + let flag = self.boolean(*value); + self.b.app(head, flag) + } + Value::Nat { value } => { + let head = self.ts("Value.nat"); + let number = self.nat_lit(value); + self.b.app(head, number) + } + Value::Int { value } => { + let head = self.ts("Value.int"); + let number = self.int_literal("Int", value); + self.b.app(head, number) + } + Value::U8 { value } => self.fixed_value("u8", "UInt8", value), + Value::U16 { value } => self.fixed_value("u16", "UInt16", value), + Value::U32 { value } => self.fixed_value("u32", "UInt32", value), + Value::U64 { value } => self.fixed_value("u64", "UInt64", value), + Value::I8 { value } => self.fixed_value("i8", "Int8", value), + Value::I16 { value } => self.fixed_value("i16", "Int16", value), + Value::I32 { value } => self.fixed_value("i32", "Int32", value), + Value::I64 { value } => self.fixed_value("i64", "Int64", value), + Value::String { value } => { + let head = self.ts("Value.string"); + let text = self.b.string(value); + self.b.app(head, text) + } + Value::Bytes { hex } => { + let head = self.ts("Value.bytes"); + let bytes = self.bytes_term(hex)?; + self.b.app(head, bytes) + } + Value::Ordering { value } => { + let head = self.ts("Value.ordering"); + let order = self.ts(match value { + OrderingValue::Lt => "Order.less", + OrderingValue::Eq => "Order.same", + OrderingValue::Gt => "Order.more", + }); + self.b.app(head, order) + } + Value::None => self.ts("Value.none"), + Value::Some { value } => { + let head = self.ts("Value.some"); + let inner = self.value_term(value)?; + self.b.app(head, inner) + } + Value::Ok { value } => { + let head = self.ts("Value.ok"); + let inner = self.value_term(value)?; + self.b.app(head, inner) + } + Value::Error { value } => { + let head = self.ts("Value.error"); + let inner = self.value_term(value)?; + self.b.app(head, inner) + } + Value::List { items } => { + let head = self.ts("Value.list"); + let ty = self.ts("Value"); + let terms = items + .iter() + .map(|item| self.value_term(item)) + .collect::, _>>()?; + let list = self.b.list(ty, &terms); + self.b.app(head, list) + } + Value::Pair { left, right } => { + let head = self.ts("Value.pair"); + let left = self.value_term(left)?; + let right = self.value_term(right)?; + self.b.apps(head, &[left, right]) + } + Value::Adt { + constructor, + fields, + } => { + let head = self.ts("Value.adt"); + let ty = self.ts("Value"); + let number = self.nat_u(*constructor); + let terms = fields + .iter() + .map(|item| self.value_term(item)) + .collect::, _>>()?; + let list = self.b.list(ty, &terms); + self.b.apps(head, &[number, list]) + } + Value::Closure { function, captures } => { + let head = self.ts("Value.closure"); + let ty = self.ts("Value"); + let number = self.nat_u(*function); + let terms = captures + .iter() + .map(|item| self.value_term(item)) + .collect::, _>>()?; + let list = self.b.list(ty, &terms); + self.b.apps(head, &[number, list]) + } + }) + } + + fn fixed_value(&mut self, constructor: &str, type_name: &str, text: &str) -> Term { + let head = self.ts(&format!("Value.{constructor}")); + let number = self.int_literal(type_name, text); + self.b.app(head, number) + } + + pub(super) fn shape_term(&mut self, shape: Shape) -> Term { + let name = match shape { + Shape::None => "none", + Shape::Some => "some", + Shape::Ok => "ok", + Shape::Error => "error", + Shape::Nil => "nil", + Shape::Cons => "cons", + Shape::Zero => "zero", + Shape::Succ => "succ", + Shape::Pair => "pair", + Shape::True => "true", + Shape::False => "false", + Shape::Unit => "unit", + Shape::Lt => "lt", + Shape::Eq => "eq", + Shape::Gt => "gt", + Shape::Adt { constructor } => { + let head = self.ts("Shape.adt"); + let number = self.nat_u(constructor); + return self.b.app(head, number); + } + }; + self.ts(&format!("Shape.{name}")) + } + + fn prim_term(&mut self, prim: &Prim) -> Term { + let name = match prim { + Prim::NatAdd => "natAdd", + Prim::NatSub => "natSub", + Prim::NatMul => "natMul", + Prim::NatQuot => "natQuot", + Prim::NatRem => "natRem", + Prim::NatEq => "natEq", + Prim::NatLe => "natLe", + Prim::NatLt => "natLt", + Prim::IntAdd => "intAdd", + Prim::IntSub => "intSub", + Prim::IntMul => "intMul", + Prim::IntNeg => "intNeg", + Prim::IntQuot => "intQuot", + Prim::IntRem => "intRem", + Prim::CheckedAdd => "checkedAdd", + Prim::CheckedSub => "checkedSub", + Prim::CheckedMul => "checkedMul", + Prim::CheckedNeg => "checkedNeg", + Prim::CheckedQuot => "checkedQuot", + Prim::BitAnd => "bitAnd", + Prim::BitOr => "bitOr", + Prim::BitXor => "bitXor", + Prim::BitNot => "bitNot", + Prim::ShiftLeft => "shiftLeft", + Prim::ShiftRight => "shiftRight", + Prim::Equal => "equal", + Prim::BoolNot => "boolNot", + Prim::BoolAnd => "boolAnd", + Prim::BoolOr => "boolOr", + Prim::Append => "append", + Prim::Length => "length", + Prim::Index => "index", + Prim::Slice => "slice", + Prim::Utf8Encode => "utf8Encode", + Prim::Utf8Decode => "utf8Decode", + Prim::CompareBytes => "compareBytes", + Prim::SplitExact => "splitExact", + Prim::Join => "join", + Prim::FormatDecimal => "formatDecimal", + Prim::Compare => "compare", + Prim::Convert { target } => { + let head = self.ts("Prim.convert"); + let kind = self.int_kind(*target); + return self.b.app(head, kind); + } + Prim::ParseDecimal { target } => { + let head = self.ts("Prim.parseDecimal"); + let ty = self.ty_term(target); + return self.b.app(head, ty); + } + }; + self.ts(&format!("Prim.{name}")) + } + + /// A match arm as a `TargetSyntax.Arm` term. + pub(super) fn arm_term(&mut self, arm: &Arm) -> Result { + let Arm { + shape, + binders, + body, + } = arm; + let head = self.ts("Arm.arm"); + let shape = self.shape_term(*shape); + let binders = self.numbers_term(binders); + let body = self.expr_term(body)?; + Ok(self.b.apps(head, &[shape, binders, body])) + } + + /// A list of match arms. + pub(super) fn arms_list(&mut self, arms: &[Arm]) -> Result { + let ty = self.ts("Arm"); + let items = arms + .iter() + .map(|arm| self.arm_term(arm)) + .collect::, _>>()?; + Ok(self.b.list(ty, &items)) + } + + fn exprs_term(&mut self, exprs: &[Expr]) -> Result { + let ty = self.ts("Expr"); + let items = exprs + .iter() + .map(|e| self.expr_term(e)) + .collect::, _>>()?; + Ok(self.b.list(ty, &items)) + } + + pub(super) fn numbers_term(&mut self, numbers: &[u64]) -> Term { + let nat = self.b.konst("Nat"); + let items: Vec = numbers.iter().map(|n| self.nat_u(*n)).collect(); + self.b.list(nat, &items) + } + + /// A program expression as a `TargetSyntax.Expr` term. + pub(super) fn expr_term(&mut self, expr: &Expr) -> Result { + let key = std::ptr::from_ref(expr); + if let Some(&done) = self.exprs.get(&key) { + return Ok(done); + } + let out = match expr { + Expr::Value { ty, value } => { + let head = self.ts("Expr.value"); + let ty = self.ty_term(ty); + let value = self.value_term(value)?; + self.b.apps(head, &[ty, value]) + } + Expr::Var { name } => { + let head = self.ts("Expr.var"); + let name = self.nat_u(*name); + self.b.app(head, name) + } + Expr::Let { + name, + ty, + bound, + body, + } => { + let head = self.ts("Expr.let"); + let name = self.nat_u(*name); + let ty = self.ty_term(ty); + let bound = self.expr_term(bound)?; + let body = self.expr_term(body)?; + self.b.apps(head, &[name, ty, bound, body]) + } + Expr::Cond { + condition, + then_branch, + else_branch, + } => { + let head = self.ts("Expr.cond"); + let c = self.expr_term(condition)?; + let t = self.expr_term(then_branch)?; + let e = self.expr_term(else_branch)?; + self.b.apps(head, &[c, t, e]) + } + Expr::Match { + ty, + scrutinee, + arms, + } => { + let head = self.ts("Expr.match"); + let ty = self.ty_term(ty); + let scrutinee = self.expr_term(scrutinee)?; + let arms = self.arms_list(arms)?; + self.b.apps(head, &[ty, scrutinee, arms]) + } + Expr::Build { + shape, + ty, + operands, + } => { + let head = self.ts("Expr.build"); + let shape = self.shape_term(*shape); + let ty = self.ty_term(ty); + let operands = self.exprs_term(operands)?; + self.b.apps(head, &[shape, ty, operands]) + } + Expr::Call { function, operands } => { + let head = self.ts("Expr.call"); + let function = self.nat_u(*function); + let operands = self.exprs_term(operands)?; + self.b.apps(head, &[function, operands]) + } + Expr::Closure { function, captures } => { + let head = self.ts("Expr.closure"); + let function = self.nat_u(*function); + let captures = self.exprs_term(captures)?; + self.b.apps(head, &[function, captures]) + } + Expr::Apply { target, operands } => { + let head = self.ts("Expr.apply"); + let target = self.expr_term(target)?; + let operands = self.exprs_term(operands)?; + self.b.apps(head, &[target, operands]) + } + Expr::Prim { + operation, + operands, + } => { + let head = self.ts("Expr.prim"); + let operation = self.prim_term(operation); + let operands = self.exprs_term(operands)?; + self.b.apps(head, &[operation, operands]) + } + Expr::First { value } => { + let head = self.ts("Expr.first"); + let value = self.expr_term(value)?; + self.b.app(head, value) + } + Expr::Second { value } => { + let head = self.ts("Expr.second"); + let value = self.expr_term(value)?; + self.b.app(head, value) + } + Expr::Field { value, index } => { + let head = self.ts("Expr.field"); + let value = self.expr_term(value)?; + let index = self.nat_u(*index); + self.b.apps(head, &[value, index]) + } + }; + self.exprs.insert(key, out); + Ok(out) + } + + /// The function `function` of the program as a `TargetSyntax.Function`. + pub(super) fn function_term(&mut self, function: &Function) -> Result { + let Function { + parameters, + types, + result, + body, + } = function; + let head = self.ts("Function.mk"); + let parameters = self.numbers_term(parameters); + let ty = self.ts("Ty"); + let items: Vec = types.iter().map(|t| self.ty_term(t)).collect(); + let types = self.b.list(ty, &items); + let result = self.ty_term(result); + let body = self.expr_term(body)?; + Ok(self.b.apps(head, &[parameters, types, result, body])) + } + + /// `__prog`'s value: the lowered program as a `TargetSyntax.Program`. + pub(super) fn program_value(&mut self) -> Result { + let program: &Program = self.gen.program; + let adt_ty = self.ts("Adt"); + let ty = self.ts("Ty"); + let ty_list = self.b.list_ty(ty); + let mut adts = Vec::new(); + for adt in &program.adts { + let mut constructors = Vec::new(); + for fields in &adt.constructors { + let items: Vec = fields.iter().map(|f| self.ty_term(f)).collect(); + constructors.push(self.b.list(ty, &items)); + } + let list = self.b.list(ty_list, &constructors); + let mk = self.ts("Adt.mk"); + adts.push(self.b.app(mk, list)); + } + let function_ty = self.ts("Function"); + let mut functions = Vec::new(); + for function in &program.functions { + functions.push(self.function_term(function)?); + } + let adts = self.b.list(adt_ty, &adts); + let functions = self.b.list(function_ty, &functions); + let mk = self.ts("Program.mk"); + Ok(self.b.apps(mk, &[adts, functions])) + } +} diff --git a/crates/lexlean/src/production/certificate/data_a/matching.rs b/crates/lexlean/src/production/certificate/data_a/matching.rs new file mode 100644 index 000000000..da5958c7b --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/matching.rs @@ -0,0 +1,311 @@ +//! The proof of a `match`: `conv_matchV` over the scrutinee, whose arms are +//! the eliminator of the scrutinee's type applied to one derivation of the +//! program's arm list per constructor (SPEC.md §17.17.4). + +use super::{Pf, Scope, Writer, EXPLICIT}; +use crate::calculus::{Arm, Expr}; +use crate::ir::core::CoreLevel; +use crate::ir::semantic::{SemanticBranch, SemanticTerm, SemanticType}; +use crate::production::kernel::{Given, Term}; +use crate::production::source::Constructor; + +impl<'a> Writer<'_, 'a> { + /// The derivation that the arm at `position` of `arms` is taken for the + /// value `encoded`: the arm matches, binds `fields` over `env`, and its + /// body is derived by `body`. + #[allow(clippy::too_many_arguments)] + pub(super) fn arm_hit( + &mut self, + env: Term, + encoded: Term, + arms: &'a [Arm], + position: usize, + fields: &[Term], + extended: Term, + body: Pf, + ) -> Result { + let arm = arms.get(position).ok_or("a match lowered to fewer arms")?; + let shape = self.shape_term(arm.shape); + let binders = self.numbers_term(&arm.binders); + let body_expr = self.expr_term(&arm.body)?; + let rest = self.arms_list(&arms[position + 1..])?; + let value_ty = self.value_ty(); + let list = self.b.list(value_ty, fields); + let p = self.prog; + self.apply( + "convA_hit", + &[ + ("p", p), + ("env", env), + ("v", encoded), + ("shape", shape), + ("binders", binders), + ("body", body_expr), + ("rest", rest), + ("fields", list), + ("env'", extended), + ], + &[ + Given::Refl, + Given::Refl, + Given::Proof(body.proof, body.ty), + ], + ) + } + + /// The derivation that the arm at `position` of `arms` is missed for the + /// value `encoded`, and a later arm is taken. + pub(super) fn arm_miss( + &mut self, + env: Term, + encoded: Term, + arms: &'a [Arm], + position: usize, + later: Pf, + ) -> Result { + let arm = arms.get(position).ok_or("a match lowered to fewer arms")?; + let shape = self.shape_term(arm.shape); + let binders = self.numbers_term(&arm.binders); + let body_expr = self.expr_term(&arm.body)?; + let rest = self.arms_list(&arms[position + 1..])?; + let p = self.prog; + self.apply( + "convA_miss", + &[ + ("p", p), + ("env", env), + ("v", encoded), + ("shape", shape), + ("binders", binders), + ("body", body_expr), + ("rest", rest), + ], + &[Given::Refl, Given::Proof(later.proof, later.ty)], + ) + } + + /// `(name, value) :: env` for each binder in order. + pub(super) fn extend_env( + &mut self, + env: Term, + binders: &[u64], + fields: &[Term], + ) -> Result { + if binders.len() != fields.len() { + return Err(format!( + "an arm binds {} names to {} fields", + binders.len(), + fields.len() + )); + } + let nat = self.b.konst("Nat"); + let value_ty = self.value_ty(); + let entry_ty = self.b.pair_ty(nat, value_ty); + let cons = self.b.konst0("List.cons"); + let mut out = env; + for (number, field) in binders.iter().zip(fields) { + let name = self.nat_u(*number); + let entry = self.b.pair(nat, value_ty, name, *field); + out = self.b.apps(cons, &[entry_ty, entry, out]); + } + Ok(out) + } + + /// The value of the constructor a branch matches, on the free variables + /// of its fields. + pub(super) fn constructor_value( + &mut self, + constructor: &Constructor, + scrutinee_ty: &SemanticType, + branch: &SemanticBranch, + frees: &[Term], + scope: &Scope, + ) -> Result { + self.constructed( + constructor, + scrutinee_ty, + &branch.constructor, + &scope.ctx.site, + frees, + ) + } + + /// The proof of a `match` the lowering made a `match` of. + #[allow(clippy::too_many_lines)] + pub(super) fn matching( + &mut self, + whole: &SemanticTerm, + scrutinee: &SemanticTerm, + branches: &[SemanticBranch], + expr: &'a Expr, + scope: &Scope, + ) -> Result { + let (match_ty, scrutinee_expr, arms) = match expr { + Expr::Match { + ty, + scrutinee, + arms, + } => (ty, scrutinee.as_ref(), arms.as_slice()), + _ => return Err("the lowering of a match is not a match".to_owned()), + }; + let scrutinee_ty = self.infer(scrutinee, scope)?; + let result_ty = self.infer(whole, scope)?; + if matches!( + scrutinee_ty, + SemanticType::ContractViolation | SemanticType::ReasoningFailure + ) { + return self.violation_matching( + &result_ty, + match_ty, + scrutinee, + branches, + arms, + scrutinee_expr, + scope, + ); + } + let (p, env) = (self.prog, scope.env); + let lean_scrutinee = self.lean_ty(&scrutinee_ty)?; + let result_lean = self.lean_ty(&result_ty)?; + let scrutinee_enc = self.enc(&scrutinee_ty)?; + let result_enc = self.enc(&result_ty)?; + let tested = self.prove(scrutinee, scrutinee_expr, scope)?; + let scrutinee_src = self.src(scrutinee, scope)?; + let bound = self.b.free(); + let hypothesis = self.b.free(); + let bool = self.b.konst("Bool"); + // Per constructor, in declaration order: the arm's width predicate, + // value, and derivation, each abstracted over the arm's fields. + let mut fits_minors: Vec<(usize, Term)> = Vec::new(); + let mut value_minors: Vec<(usize, Term)> = Vec::new(); + let mut proof_minors: Vec<(usize, Term)> = Vec::new(); + for (position, branch) in branches.iter().enumerate() { + let locals = self.gen.branch_locals(branch, scrutinee, &scope.ctx)?; + let mut frees = Vec::new(); + let mut types = Vec::new(); + for (local, _) in &locals { + frees.push(self.b.free()); + types.push(self.lean_ty(&local.ty.clone())?); + } + let arm = arms.get(position).ok_or("a match lowered to fewer arms")?; + let mut fields = Vec::new(); + for ((local, _), free) in locals.iter().zip(&frees) { + let enc = self.enc(&local.ty.clone())?; + fields.push(self.b.app(enc, *free)); + } + let arm_env = self.extend_env(env, &arm.binders, &fields)?; + let inner = self.bind(scope, locals.clone(), &frees, arm_env); + let body_pf = self.prove(&branch.body, &arm.body, &inner)?; + let fits = self.fits_of(body_pf)?; + let body_src = self.src(&branch.body, &inner)?; + let (constructor, _) = + self.gen + .source + .branch(branch, &scrutinee_ty, &scope.ctx.site)?; + let value = + self.constructor_value(&constructor, &scrutinee_ty, branch, &frees, scope)?; + let encoded = self.b.app(scrutinee_enc, value); + let mut proof = self.arm_hit(env, encoded, arms, position, &fields, arm_env, body_pf)?; + for earlier in (0..position).rev() { + proof = self.arm_miss(env, encoded, arms, earlier, proof)?; + } + let ctor_position = self.constructor_position(&constructor)?; + let wrap = |writer: &mut Self, term: Term| { + let mut out = term; + for (index, (local, _)) in locals.iter().enumerate().rev() { + out = writer.b.lam_over( + &super::super::identifier(&local.name), + EXPLICIT, + types[index], + frees[index], + out, + ); + } + out + }; + fits_minors.push((ctor_position, wrap(self, fits))); + value_minors.push((ctor_position, wrap(self, body_src))); + proof_minors.push((ctor_position, wrap(self, proof.proof))); + } + let sorted = |mut minors: Vec<(usize, Term)>| -> Vec { + minors.sort_by_key(|(position, _)| *position); + minors.into_iter().map(|(_, term)| term).collect() + }; + let (fits_terms, value_terms, proof_terms) = ( + sorted(fits_minors), + sorted(value_minors), + sorted(proof_minors), + ); + // G a = the arms' predicates by cases on a; W a = the encoding of the + // arms' values by cases on a. + let g_body = self.cases_on_const(&scrutinee_ty, bound, bool, &fits_terms)?; + let g = self.b.lam_over("__a", EXPLICIT, lean_scrutinee, bound, g_body); + let w_inner = self.cases_on_const(&scrutinee_ty, bound, result_lean, &value_terms)?; + let w_body = self.b.app(result_enc, w_inner); + let w = self.b.lam_over("__a", EXPLICIT, lean_scrutinee, bound, w_body); + // ha : ∀ a, sv = a → ConvA p env (enc a) arms (Rel (G a) (W a)), + // proved by cases on a with the arms' derivations as minors. + let arms_term = self.arms_list(arms)?; + let encoded_bound = self.b.app(scrutinee_enc, bound); + let g_a = self.b.app(g, bound); + let w_a = self.b.app(w, bound); + let rel = self.rel(g_a, w_a); + let conv_a = self.lp("ConvA"); + let target = self.b.apps(conv_a, &[p, env, encoded_bound, arms_term, rel]); + let motive = self + .b + .lam_over("__a", EXPLICIT, lean_scrutinee, bound, target); + let cases = self.cases_on( + &scrutinee_ty, + bound, + motive, + CoreLevel::Zero, + &proof_terms, + )?; + let equation = self.eq(lean_scrutinee, scrutinee_src, bound); + let with_hypothesis = self + .b + .lam_over("__h", EXPLICIT, equation, hypothesis, cases); + let ha = self + .b + .lam_over("__a", EXPLICIT, lean_scrutinee, bound, with_hypothesis); + let scrutinee_expr_term = self.expr_term(scrutinee_expr)?; + let ty = self.ty_term(match_ty); + self.apply( + "conv_matchV", + &[ + ("p", p), + ("env", env), + ("α", lean_scrutinee), + ("t", ty), + ("s", scrutinee_expr_term), + ("arms", arms_term), + ], + &[ + Given::Term(scrutinee_enc), + Given::Term(g), + Given::Term(w), + Given::Term(scrutinee_src), + Given::Proof(tested.proof, tested.ty), + Given::Term(ha), + ], + ) + } + + /// The proof of a match over a violation: its pair of Booleans, then each + /// Boolean in turn (`lower::violation_match`). + #[allow(clippy::too_many_arguments)] + fn violation_matching( + &mut self, + result_ty: &SemanticType, + match_ty: &crate::calculus::Ty, + scrutinee: &SemanticTerm, + branches: &[SemanticBranch], + arms: &'a [Arm], + scrutinee_expr: &'a Expr, + scope: &Scope, + ) -> Result { + let _ = (result_ty, match_ty, scrutinee, branches, arms, scrutinee_expr, scope); + Err("violation matches".to_owned()) + } +} diff --git a/crates/lexlean/src/production/certificate/data_a/prims.rs b/crates/lexlean/src/production/certificate/data_a/prims.rs new file mode 100644 index 000000000..6ea4397d4 --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/prims.rs @@ -0,0 +1,368 @@ +//! Primitives realized directly (SPEC.md §17.17.3): the library lemma of the +//! primitive at its operand types, applied to the operands' denotations. + +use super::super::{ + element_of, equal_suffix, integer_suffix, numeric, option_value, sequence, width_suffix, + Numeric, Sequence, +}; +use super::{Pf, Scope, Writer}; +use crate::calculus::Expr; +use crate::ir::semantic::{SemanticPrimitive, SemanticTerm, SemanticType}; +use crate::production::kernel::{Given, Term}; +use crate::production::source::Site; + +/// A primitive's relation lemma, its leading hints and arguments, and the +/// library lemma's own name. +struct PrimRule { + lemma: String, + hints: Vec<(&'static str, Term)>, + prefix: Vec, +} + +impl<'a> Writer<'_, 'a> { + /// The encoder bundle of a list, set, or map type. + pub(super) fn list_bundle(&mut self, ty: &SemanticType) -> Result { + match self.gen.nested.get(&self.gen.source.type_text(ty)) { + Some(nested) => { + let index = nested.index; + Ok(self.own(&format!("__L_{index}"))) + } + None => { + let element = element_of(ty)?; + let alpha = self.lean_ty(&element)?; + let e = self.enc(&element)?; + let head = self.lp("listEnc"); + Ok(self.b.apps(head, &[alpha, e])) + } + } + } + + /// The encoder bundle of an option type. + pub(super) fn option_bundle(&mut self, ty: &SemanticType) -> Result { + match self.gen.nested.get(&self.gen.source.type_text(ty)) { + Some(nested) => { + let index = nested.index; + Ok(self.own(&format!("__O_{index}"))) + } + None => { + let value = option_value(ty)?; + let alpha = self.lean_ty(&value)?; + let e = self.enc(&value)?; + let head = self.lp("optEnc"); + Ok(self.b.apps(head, &[alpha, e])) + } + } + } + + /// The relation lemma of a directly realized primitive at its operand + /// types. + #[allow(clippy::too_many_lines)] + fn prim_rule( + &mut self, + operation: SemanticPrimitive, + types: &[SemanticType], + result: &SemanticType, + site: &Site, + ) -> Result { + let first = types + .first() + .ok_or_else(|| format!("{operation:?} has no operand"))?; + let plain = |lemma: String| PrimRule { + lemma, + hints: Vec::new(), + prefix: Vec::new(), + }; + let numeric_rule = |nat: &str, int: &str| -> Result { + Ok(match numeric(first)? { + Numeric::Nat => plain(nat.to_owned()), + Numeric::Int => plain(int.to_owned()), + }) + }; + Ok(match operation { + SemanticPrimitive::Subtract => numeric_rule("prim_natSub", "prim_intSub")?, + SemanticPrimitive::Multiply => numeric_rule("prim_natMul", "prim_intMul")?, + SemanticPrimitive::Quotient => numeric_rule("prim_natQuot", "prim_intQuot")?, + SemanticPrimitive::Remainder => numeric_rule("prim_natRem", "prim_intRem")?, + SemanticPrimitive::Negate => plain("prim_intNeg".to_owned()), + SemanticPrimitive::CheckedConvert => plain(format!( + "prim_convert_{}_{}", + integer_suffix(first)?, + width_suffix(&option_value(result)?)? + )), + SemanticPrimitive::CheckedAdd => { + plain(format!("prim_checkedAdd_{}", width_suffix(first)?)) + } + SemanticPrimitive::CheckedSubtract => { + plain(format!("prim_checkedSub_{}", width_suffix(first)?)) + } + // The 64-bit signed loops are taken by their clauses (§17.17.3). + SemanticPrimitive::CheckedMultiply => { + let suffix = width_suffix(first)?; + let mut rule = plain(format!("prim_checkedMul_{suffix}")); + if suffix == "i64" { + let step = self.runtime_const(site, "multiplyMagnitudeInt64")?; + rule.prefix = vec![Given::Term(step), Given::Refl, Given::Refl]; + } + rule + } + SemanticPrimitive::CheckedNegate => { + plain(format!("prim_checkedNeg_{}", width_suffix(first)?)) + } + SemanticPrimitive::CheckedQuotient => { + let suffix = width_suffix(first)?; + let mut rule = plain(format!("prim_checkedQuot_{suffix}")); + if suffix == "i64" { + let step = self.runtime_const(site, "divideMagnitudeInt64")?; + rule.prefix = vec![Given::Term(step), Given::Refl, Given::Refl]; + } + rule + } + SemanticPrimitive::BitAnd => plain(format!("prim_bitAnd_{}", width_suffix(first)?)), + SemanticPrimitive::BitOr => plain(format!("prim_bitOr_{}", width_suffix(first)?)), + SemanticPrimitive::BitXor => plain(format!("prim_bitXor_{}", width_suffix(first)?)), + SemanticPrimitive::BitNot => plain(format!("prim_bitNot_{}", width_suffix(first)?)), + SemanticPrimitive::ShiftLeft => { + plain(format!("prim_shiftLeft_{}", width_suffix(first)?)) + } + SemanticPrimitive::ShiftRight => { + plain(format!("prim_shiftRight_{}", width_suffix(first)?)) + } + SemanticPrimitive::Append => match sequence(first)? { + Sequence::List(element) => { + let bundle = self.list_bundle(first)?; + let alpha = self.lean_ty(&element)?; + PrimRule { + lemma: "prim_append_list".to_owned(), + hints: vec![("α", alpha)], + prefix: vec![Given::Term(bundle)], + } + } + Sequence::Bytes => plain("prim_append_bytes".to_owned()), + Sequence::String => return Err("Append of strings".to_owned()), + }, + SemanticPrimitive::Length => match sequence(first)? { + Sequence::List(element) => { + let bundle = self.list_bundle(first)?; + let alpha = self.lean_ty(&element)?; + PrimRule { + lemma: "prim_length_list".to_owned(), + hints: vec![("α", alpha)], + prefix: vec![Given::Term(bundle)], + } + } + Sequence::Bytes => plain("prim_length_bytes".to_owned()), + Sequence::String => plain("prim_length_string".to_owned()), + }, + SemanticPrimitive::MapSize | SemanticPrimitive::SetSize => { + let bundle = self.list_bundle(first)?; + let alpha = self.lean_ty(&element_of(first)?)?; + PrimRule { + lemma: "prim_length_list".to_owned(), + hints: vec![("α", alpha)], + prefix: vec![Given::Term(bundle)], + } + } + SemanticPrimitive::Index => match sequence(first)? { + Sequence::List(element) => { + let bundle = self.list_bundle(first)?; + let options = self.option_bundle(&SemanticType::Option { + value: Box::new(element.clone()), + })?; + let alpha = self.lean_ty(&element)?; + let index = self.runtime_const(site, "listIndex")?; + PrimRule { + lemma: "prim_index_list".to_owned(), + hints: vec![("α", alpha)], + prefix: vec![ + Given::Term(bundle), + Given::Term(options), + Given::Refl, + Given::Term(index), + Given::Refl, + Given::Refl, + Given::Refl, + ], + } + } + Sequence::Bytes => plain("prim_index_bytes".to_owned()), + Sequence::String => return Err("Index of a string".to_owned()), + }, + SemanticPrimitive::Slice => match sequence(first)? { + Sequence::List(_) => { + let bundle = self.list_bundle(first)?; + let options = self.option_bundle(&SemanticType::Option { + value: Box::new(first.clone()), + })?; + let alpha = self.lean_ty(&element_of(first)?)?; + PrimRule { + lemma: "prim_slice_list".to_owned(), + hints: vec![("α", alpha)], + prefix: vec![Given::Term(bundle), Given::Term(options), Given::Refl], + } + } + Sequence::Bytes => plain("prim_slice_bytes".to_owned()), + Sequence::String => return Err("Slice of a string".to_owned()), + }, + SemanticPrimitive::Utf8Encode => plain("prim_utf8Encode".to_owned()), + SemanticPrimitive::Utf8Decode => plain("prim_utf8Decode".to_owned()), + SemanticPrimitive::CompareBytes => plain("prim_compareBytes".to_owned()), + SemanticPrimitive::Equal => plain(format!("prim_equal_{}", equal_suffix(first)?)), + SemanticPrimitive::SplitExact => plain("prim_splitExact".to_owned()), + SemanticPrimitive::Join => plain("prim_join".to_owned()), + SemanticPrimitive::ParseDecimal => { + let target = option_value(result)?; + match numeric(&target) { + Ok(Numeric::Int) => plain("prim_parseDecimal_int".to_owned()), + Ok(Numeric::Nat) | Err(_) => { + plain(format!("prim_parseDecimal_{}", width_suffix(&target)?)) + } + } + } + SemanticPrimitive::FormatDecimal => { + plain(format!("prim_formatDecimal_{}", integer_suffix(first)?)) + } + SemanticPrimitive::MapEntries + | SemanticPrimitive::SetElements + | SemanticPrimitive::MapInsert + | SemanticPrimitive::MapRemove + | SemanticPrimitive::MapLookup + | SemanticPrimitive::MapContains + | SemanticPrimitive::MapKeys + | SemanticPrimitive::MapValues + | SemanticPrimitive::MapFold + | SemanticPrimitive::SetInsert + | SemanticPrimitive::SetRemove + | SemanticPrimitive::SetContains + | SemanticPrimitive::SetUnion + | SemanticPrimitive::SetIntersection + | SemanticPrimitive::SetDifference + | SemanticPrimitive::SetFold + | SemanticPrimitive::ListFold + | SemanticPrimitive::Iterate + | SemanticPrimitive::IterateUntil + | SemanticPrimitive::GraphSuccessors + | SemanticPrimitive::GraphReachable + | SemanticPrimitive::GraphTopological + | SemanticPrimitive::LessThan => { + return Err(format!("{operation:?} is not a direct primitive")); + } + }) + } + + /// A runtime constant of the module `site` is read in. + fn runtime_const(&mut self, site: &Site, name: &str) -> Result { + let module = self.gen.source.lean_module(&site.module)?; + Ok(self.b.konst(&format!("{module}.LexLeanRuntime.{name}"))) + } + + /// The proof of a primitive term. + pub(super) fn primitive( + &mut self, + whole: &SemanticTerm, + operation: SemanticPrimitive, + arguments: &[SemanticTerm], + result: &SemanticType, + expr: &'a Expr, + scope: &Scope, + ) -> Result { + let site = scope.ctx.site.clone(); + let types = arguments + .iter() + .map(|argument| self.infer(argument, scope)) + .collect::, _>>()?; + let result = self.gen.source.close(result, &site); + if let Some((template, instance)) = + super::super::super::lower::template_of(operation, &types)? + { + return self.template_call(template, &instance, arguments, &types, expr, scope); + } + match operation { + SemanticPrimitive::MapEntries | SemanticPrimitive::SetElements => { + if arguments.len() != 1 { + return Err(format!("{operation:?} takes one operand")); + } + return self.prove(&arguments[0], expr, scope); + } + SemanticPrimitive::LessThan => { + return self.less_than(&types, arguments, expr, scope); + } + SemanticPrimitive::Subtract + | SemanticPrimitive::Multiply + | SemanticPrimitive::Quotient + | SemanticPrimitive::Remainder + | SemanticPrimitive::Negate + | SemanticPrimitive::CheckedConvert + | SemanticPrimitive::CheckedAdd + | SemanticPrimitive::CheckedSubtract + | SemanticPrimitive::CheckedMultiply + | SemanticPrimitive::CheckedNegate + | SemanticPrimitive::CheckedQuotient + | SemanticPrimitive::BitAnd + | SemanticPrimitive::BitOr + | SemanticPrimitive::BitXor + | SemanticPrimitive::BitNot + | SemanticPrimitive::ShiftLeft + | SemanticPrimitive::ShiftRight + | SemanticPrimitive::Append + | SemanticPrimitive::Length + | SemanticPrimitive::Index + | SemanticPrimitive::Slice + | SemanticPrimitive::Utf8Encode + | SemanticPrimitive::Utf8Decode + | SemanticPrimitive::CompareBytes + | SemanticPrimitive::Equal + | SemanticPrimitive::SplitExact + | SemanticPrimitive::Join + | SemanticPrimitive::ParseDecimal + | SemanticPrimitive::FormatDecimal + | SemanticPrimitive::MapSize + | SemanticPrimitive::SetSize + | SemanticPrimitive::MapInsert + | SemanticPrimitive::MapRemove + | SemanticPrimitive::MapLookup + | SemanticPrimitive::MapContains + | SemanticPrimitive::MapKeys + | SemanticPrimitive::MapValues + | SemanticPrimitive::MapFold + | SemanticPrimitive::SetInsert + | SemanticPrimitive::SetRemove + | SemanticPrimitive::SetContains + | SemanticPrimitive::SetUnion + | SemanticPrimitive::SetIntersection + | SemanticPrimitive::SetDifference + | SemanticPrimitive::SetFold + | SemanticPrimitive::ListFold + | SemanticPrimitive::Iterate + | SemanticPrimitive::IterateUntil + | SemanticPrimitive::GraphSuccessors + | SemanticPrimitive::GraphReachable + | SemanticPrimitive::GraphTopological => {} + } + let _ = whole; + let operand_exprs = match expr { + Expr::Prim { operands, .. } if operands.len() == arguments.len() => operands, + _ => return Err("the lowering of a primitive is not a primitive".to_owned()), + }; + let rule = self.prim_rule(operation, &types, &result, &site)?; + let mut proofs = Vec::new(); + let mut sources = Vec::new(); + for (argument, child) in arguments.iter().zip(operand_exprs) { + proofs.push(self.prove(argument, child, scope)?); + sources.push(self.src(argument, scope)?); + } + let list = self.operands(scope.env, &proofs)?; + let mut given = rule.prefix; + given.extend(sources.into_iter().map(Given::Term)); + let hints: Vec<(&str, Term)> = rule.hints; + let lemma = self.apply(&rule.lemma, &hints, &given)?; + let (p, env) = (self.prog, scope.env); + self.apply( + "conv_prim", + &[("p", p), ("env", env)], + &[ + Given::Proof(list.proof, list.ty), + Given::Proof(lemma.proof, lemma.ty), + ], + ) + } +} diff --git a/crates/lexlean/src/production/certificate/data_a/prove.rs b/crates/lexlean/src/production/certificate/data_a/prove.rs new file mode 100644 index 000000000..45c9c83d2 --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/prove.rs @@ -0,0 +1,953 @@ +//! The proof term of a source term's relation (SPEC.md §17.17.4): one lemma +//! of the preservation library per construct, applied at the program +//! expression the lowering produced for it. + +use super::super::{element_of, Ctx}; +use super::{CompTerms, LocalTerm, Pf, Scope, Writer, EXPLICIT}; +use crate::calculus::{Arm, Expr, Shape, Ty}; +use crate::ir::semantic::{ + SemanticBranch, SemanticEdge, SemanticMapEntry, SemanticPrimitive, SemanticTerm, SemanticType, +}; +use crate::production::kernel::{Given, Term}; +use crate::production::source::{Constructor, Local, Source}; + +/// What the proof of an argument contributes to its callee's relation. +pub(super) struct Passed { + /// The arguments the callee's width predicate takes for it. + pub(super) fits: Vec, + /// The arguments the callee's relation takes for it. + pub(super) relation: Vec, + /// The proof of its operand. + pub(super) proof: Pf, +} + +/// How a conditional selects its value. +pub(super) enum Selection { + /// `if c then t else e` at a result type. + If(SemanticType), + /// `c && right`. + And, + /// `c || right`. + Or, +} + +impl<'a> Writer<'_, 'a> { + // --- Types of derivations --------------------------------------------- + + /// `Rel fits value`. + pub(super) fn rel(&mut self, fits: Term, value: Term) -> Term { + let rel = self.lp("Rel"); + self.b.apps(rel, &[fits, value]) + } + + /// `Conv p env e (Rel fits value)`. + pub(super) fn conv_ty(&mut self, env: Term, e: Term, fits: Term, value: Term) -> Term { + let conv = self.lp("Conv"); + let rel = self.rel(fits, value); + let p = self.prog; + self.b.apps(conv, &[p, env, e, rel]) + } + + /// The width predicate and value a derivation's type relates. + pub(super) fn rel_parts(&self, ty: Term) -> Result<(Term, Term), String> { + let (_, args) = self.b.spine(ty); + let observation = args.get(3).copied().ok_or("a derivation type with no relation")?; + let (_, parts) = self.b.spine(observation); + match parts.as_slice() { + [fits, value] => Ok((*fits, *value)), + _ => Err(format!( + "a derivation relates {}, not a Rel", + self.b.show(observation) + )), + } + } + + /// The width predicate of a derivation. + pub(super) fn fits_of(&self, pf: Pf) -> Result { + Ok(self.rel_parts(pf.ty)?.0) + } + + /// The value of a derivation. + pub(super) fn value_of(&self, pf: Pf) -> Result { + Ok(self.rel_parts(pf.ty)?.1) + } + + // --- Lists of operands ------------------------------------------------ + + /// The proof that the operands of a call or construction evaluate: a + /// `ConvL` chain over the proofs of each. + pub(super) fn operands(&mut self, env: Term, proofs: &[Pf]) -> Result { + let p = self.prog; + let mut out = self.apply("convL_nil", &[("p", p), ("env", env)], &[])?; + for head in proofs.iter().rev() { + out = self.apply( + "convL_cons", + &[], + &[ + Given::Proof(head.proof, head.ty), + Given::Proof(out.proof, out.ty), + ], + )?; + } + Ok(out) + } + + /// A value built: `conv_build` over the operands with the construction + /// lemma `construct`. + fn built( + &mut self, + scope: &Scope, + expr: &Expr, + operands: &[Pf], + construct: Pf, + ) -> Result { + let (shape, ty, exprs) = match expr { + Expr::Build { + shape, + ty, + operands, + } => (*shape, ty, operands), + _ => return Err("the lowering of a construction is not a build".to_owned()), + }; + if exprs.len() != operands.len() { + return Err(format!( + "a construction of {} operands lowered to a build of {}", + operands.len(), + exprs.len() + )); + } + let list = self.operands(scope.env, operands)?; + let shape = self.shape_term(shape); + let ty = self.ty_term(ty); + let (p, env) = (self.prog, scope.env); + self.apply( + "conv_build", + &[("p", p), ("env", env), ("shape", shape), ("t", ty)], + &[ + Given::Proof(list.proof, list.ty), + Given::Proof(construct.proof, construct.ty), + ], + ) + } + + /// A construction lemma that needs no argument. + fn construct_plain(&mut self, name: &str) -> Result { + self.apply(name, &[], &[]) + } + + // --- Values of locals ------------------------------------------------- + + /// The encoded value of a local: its encoding applied to it, or the closure + /// value of a function-typed one. + pub(super) fn encoded_local( + &mut self, + local: &super::super::CLocal, + terms: LocalTerm, + ) -> Result { + match terms.companion { + Some(companion) => { + let closure = self.ts("Value.closure"); + Ok(self.b.apps(closure, &[companion.index, companion.captures])) + } + None => { + let enc = self.enc(&local.local.ty)?; + Ok(self.b.app(enc, terms.value)) + } + } + } + + // --- Derivations ------------------------------------------------------ + + /// The proof of `term`, lowered to `expr`. + #[allow(clippy::too_many_lines)] + pub(super) fn prove( + &mut self, + term: &SemanticTerm, + expr: &'a Expr, + scope: &Scope, + ) -> Result { + let site = scope.ctx.site.clone(); + let (p, env) = (self.prog, scope.env); + match term { + SemanticTerm::Var { name } => { + let number = match expr { + Expr::Var { name } => *name, + _ => return Err(format!("the lowering of `{name}` is not a variable")), + }; + let (local, terms) = scope.lookup(name)?; + let local = local.clone(); + let value = self.encoded_local(&local, terms)?; + let x = self.nat_u(number); + self.apply( + "conv_var", + &[("p", p), ("env", env), ("x", x), ("v", value)], + &[Given::Refl], + ) + } + SemanticTerm::Nat { value: _ } + | SemanticTerm::Integer { + representation: _, + value: _, + } + | SemanticTerm::String { value: _ } + | SemanticTerm::Bytes { hex: _ } => { + let (ty, value) = match expr { + Expr::Value { ty, value } => (ty, value), + _ => return Err("the lowering of a literal is not a value".to_owned()), + }; + let ty = self.ty_term(ty); + let value = self.value_term(value)?; + self.apply( + "conv_value", + &[("p", p), ("env", env), ("t", ty), ("v", value)], + &[], + ) + } + SemanticTerm::Primitive { + operation, + arguments, + result, + } => self.primitive(term, *operation, arguments, result, expr, scope), + SemanticTerm::Bool { value } => { + let construct = self.construct_plain(if *value { + "construct_true" + } else { + "construct_false" + })?; + self.built(scope, expr, &[], construct) + } + SemanticTerm::Unit => { + let construct = self.construct_plain("construct_unit")?; + self.built(scope, expr, &[], construct) + } + SemanticTerm::Nil { element: _ } => { + let construct = self.construct_plain("construct_nil")?; + self.built(scope, expr, &[], construct) + } + SemanticTerm::Cons { head, tail } => { + let (head_expr, tail_expr) = match expr { + Expr::Build { operands, .. } if operands.len() == 2 => { + (&operands[0], &operands[1]) + } + _ => return Err("the lowering of a cons is not a build of two".to_owned()), + }; + let ty = self.infer(term, scope)?; + let head_pf = self.prove(head, head_expr, scope)?; + let tail_pf = self.prove(tail, tail_expr, scope)?; + let tail_src = self.src(tail, scope)?; + let construct = self.construct_cons(&ty, head_pf, tail_src)?; + self.built(scope, expr, &[head_pf, tail_pf], construct) + } + SemanticTerm::Record { + r#type: _, + type_arguments: _, + fields, + } => { + let ty = self.infer(term, scope)?; + self.record_proof(&ty, fields, expr, scope) + } + SemanticTerm::Constructor { + constructor, + type_arguments, + arguments, + } => { + let closed: Vec = type_arguments + .iter() + .map(|argument| self.gen.source.close(argument, &site)) + .collect(); + let (kind, _) = self.gen.source.constructor(constructor, &closed, &site)?; + let operand_exprs = self.operand_exprs(expr)?; + let mut proofs = Vec::new(); + match &kind { + Constructor::Violation(first, second) => { + for (value, child) in [*first, *second].into_iter().zip(operand_exprs) { + let construct = self.construct_plain(if value { + "construct_true" + } else { + "construct_false" + })?; + proofs.push(self.built(scope, child, &[], construct)?); + } + } + Constructor::Bool(_) + | Constructor::Zero + | Constructor::Succ + | Constructor::Nil + | Constructor::Cons + | Constructor::OptionNone + | Constructor::OptionSome + | Constructor::Ok + | Constructor::Error + | Constructor::Document { ty: _, index: _ } => { + for (argument, child) in arguments.iter().zip(operand_exprs) { + proofs.push(self.prove(argument, child, scope)?); + } + } + } + let construct = match &kind { + Constructor::Bool(true) => self.construct_plain("construct_true")?, + Constructor::Bool(false) => self.construct_plain("construct_false")?, + Constructor::Zero => self.construct_plain("construct_zero")?, + Constructor::Nil => self.construct_plain("construct_nil")?, + Constructor::OptionNone => self.construct_plain("construct_none")?, + Constructor::Succ => { + let n = self.src(&arguments[0], scope)?; + self.apply("construct_succ", &[("n", n)], &[])? + } + Constructor::Cons => { + let ty = self.infer(term, scope)?; + let tail = self.src(&arguments[1], scope)?; + self.construct_cons(&ty, proofs[0], tail)? + } + Constructor::OptionSome => { + let a = self.value_of(proofs[0])?; + self.apply("construct_some", &[("a", a)], &[])? + } + Constructor::Ok => { + let a = self.value_of(proofs[0])?; + self.apply("construct_ok", &[("a", a)], &[])? + } + Constructor::Error => { + let a = self.value_of(proofs[0])?; + self.apply("construct_error", &[("a", a)], &[])? + } + Constructor::Violation(_, _) => { + let a = self.value_of(proofs[0])?; + let b = self.value_of(proofs[1])?; + self.apply("construct_pair", &[("a", a), ("b", b)], &[])? + } + Constructor::Document { ty: _, index } => { + self.construct_adt(*index as u64, &proofs)? + } + }; + self.built(scope, expr, &proofs, construct) + } + SemanticTerm::InstanceValue { + class: _, + arguments: _, + resolved, + } => { + let module = Source::module_of(resolved, &site); + let index = self.gen.instance_index(&module, &resolved.name, &[])?; + let function = match expr { + Expr::Call { function, .. } => *function, + _ => return Err("the lowering of an instance is not a call".to_owned()), + }; + if function != index { + return Err(format!( + "an instance at function {index} lowered to a call of {function}" + )); + } + let list = self.operands(env, &[])?; + let rel = self.call_relation(index, &[])?; + self.apply( + "conv_call", + &[("p", p), ("env", env)], + &[ + Given::Proof(list.proof, list.ty), + Given::Proof(rel.proof, rel.ty), + ], + ) + } + SemanticTerm::Project { value, field } => { + let (inner_expr, index) = match expr { + Expr::Field { value, index } => (value.as_ref(), *index), + _ => return Err("the lowering of a projection is not a field".to_owned()), + }; + let ty = self.infer(value, scope)?; + let shape = self.gen.source.document(&ty)?; + let position = shape + .field_names + .iter() + .position(|name| name == field) + .ok_or_else(|| format!("`{field}` is not a field"))?; + let inner = self.prove(value, inner_expr, scope)?; + // The encoding of the document is `Value.adt 0` of its + // fields' encodings; the projected field is the one at + // `index`. + let src = self.src(value, scope)?; + let mut fields = Vec::new(); + for (name, field_ty) in shape.field_names.iter().zip(&shape.fields[0]) { + let projected = self.project_term(&ty, name, src)?; + let enc = self.enc(field_ty)?; + fields.push(self.b.app(enc, projected)); + } + let value_ty = self.value_ty(); + let list = self.b.list(value_ty, &fields); + let selected = fields[position]; + let ctor = self.nat_u(0); + let k = self.nat_u(index); + self.apply( + "conv_field", + &[ + ("p", p), + ("env", env), + ("k", k), + ("fields", list), + ("ctor", ctor), + ("v", selected), + ], + &[Given::Proof(inner.proof, inner.ty), Given::Refl], + ) + } + SemanticTerm::Call { + function, + type_arguments, + arguments, + } => { + let module = Source::module_of(function, &site); + let closed: Vec = type_arguments + .iter() + .map(|argument| self.gen.source.close(argument, &site)) + .collect(); + let index = self.gen.instance_index(&module, &function.name, &closed)?; + let (callee, operand_exprs) = match expr { + Expr::Call { function, operands } => (*function, operands), + _ => return Err("the lowering of a call is not a call".to_owned()), + }; + if callee != index { + return Err(format!("a call of function {index} lowered to {callee}")); + } + let mut proofs = Vec::new(); + let mut relation_args = Vec::new(); + let mut fits_args = Vec::new(); + for (argument, child) in arguments.iter().zip(operand_exprs) { + let passed = self.argument(argument, child, scope)?; + proofs.push(passed.proof); + relation_args.extend(passed.relation); + fits_args.extend(passed.fits); + } + let list = self.operands(env, &proofs)?; + let rel = self.call_relation(index, &relation_args)?; + self.apply( + "conv_call", + &[("p", p), ("env", env)], + &[ + Given::Proof(list.proof, list.ty), + Given::Proof(rel.proof, rel.ty), + ], + ) + } + SemanticTerm::If { + condition, + then_value, + else_value, + } => { + let (c, t, e) = match expr { + Expr::Cond { + condition, + then_branch, + else_branch, + } => (condition.as_ref(), then_branch.as_ref(), else_branch.as_ref()), + _ => return Err("the lowering of a conditional is not a cond".to_owned()), + }; + self.conditional(term, condition, then_value, else_value, (c, t, e), scope) + } + SemanticTerm::Match { + scrutinee, + branches, + } => self.matching(term, scrutinee, branches, expr, scope), + SemanticTerm::Add { left, right } => { + self.binary_prim(left, right, expr, scope, |writer, a, b| { + writer.apply("prim_natAdd", &[], &[Given::Term(a), Given::Term(b)]) + }) + } + SemanticTerm::Beq { left, right } => { + self.binary_prim(left, right, expr, scope, |writer, a, b| { + writer.apply("prim_natEq", &[], &[Given::Term(a), Given::Term(b)]) + }) + } + SemanticTerm::Ble { left, right } => { + self.binary_prim(left, right, expr, scope, |writer, a, b| { + writer.apply("prim_natLe", &[], &[Given::Term(a), Given::Term(b)]) + }) + } + SemanticTerm::Blt { left, right } => { + self.binary_prim(left, right, expr, scope, |writer, a, b| { + writer.apply("prim_natLt", &[], &[Given::Term(a), Given::Term(b)]) + }) + } + SemanticTerm::And { left, right } => { + let (c, t, e) = match expr { + Expr::Cond { + condition, + then_branch, + else_branch, + } => (condition.as_ref(), then_branch.as_ref(), else_branch.as_ref()), + _ => return Err("the lowering of && is not a cond".to_owned()), + }; + let else_term = SemanticTerm::Bool { value: false }; + self.conditional_of(Selection::And, left, right, &else_term, (c, t, e), scope) + } + SemanticTerm::Or { left, right } => { + let (c, t, e) = match expr { + Expr::Cond { + condition, + then_branch, + else_branch, + } => (condition.as_ref(), then_branch.as_ref(), else_branch.as_ref()), + _ => return Err("the lowering of || is not a cond".to_owned()), + }; + let then_term = SemanticTerm::Bool { value: true }; + self.conditional_of(Selection::Or, left, &then_term, right, (c, t, e), scope) + } + SemanticTerm::Not { value } => { + let operand_exprs = match expr { + Expr::Prim { operands, .. } => operands, + _ => return Err("the lowering of ! is not a primitive".to_owned()), + }; + let inner = self.prove(value, &operand_exprs[0], scope)?; + let src = self.src(value, scope)?; + let list = self.operands(env, &[inner])?; + let lemma = self.apply("prim_boolNot", &[], &[Given::Term(src)])?; + self.apply( + "conv_prim", + &[("p", p), ("env", env)], + &[ + Given::Proof(list.proof, list.ty), + Given::Proof(lemma.proof, lemma.ty), + ], + ) + } + SemanticTerm::Let { + binder, + value, + body, + } => self.let_proof(binder, value, body, expr, scope), + SemanticTerm::Pair { left, right } => { + let operand_exprs = self.operand_exprs(expr)?; + let l = self.prove(left, &operand_exprs[0], scope)?; + let r = self.prove(right, &operand_exprs[1], scope)?; + let a = self.value_of(l)?; + let b = self.value_of(r)?; + let construct = self.apply("construct_pair", &[("a", a), ("b", b)], &[])?; + self.built(scope, expr, &[l, r], construct) + } + SemanticTerm::First { value } | SemanticTerm::Second { value } => { + let inner_expr = match expr { + Expr::First { value } | Expr::Second { value } => value.as_ref(), + _ => return Err("the lowering of a projection is not first or second".to_owned()), + }; + let inner = self.prove(value, inner_expr, scope)?; + let name = if matches!(term, SemanticTerm::First { value: _ }) { + "conv_first" + } else { + "conv_second" + }; + self.apply( + name, + &[("p", p), ("env", env)], + &[Given::Proof(inner.proof, inner.ty)], + ) + } + SemanticTerm::Apply { + function, + arguments, + } => self.application(function, arguments, expr, scope), + SemanticTerm::MapLiteral { + key: _, + value: _, + entries, + } => { + let mut items = Vec::new(); + let mut cursor = Some(expr); + for SemanticMapEntry { key, value } in entries { + let node = cursor.ok_or("a map literal lowered to a shorter chain")?; + let (item_expr, rest) = self.chain(node)?; + let operand_exprs = self.operand_exprs(item_expr)?; + let k = self.prove(key, &operand_exprs[0], scope)?; + let v = self.prove(value, &operand_exprs[1], scope)?; + let a = self.value_of(k)?; + let b = self.value_of(v)?; + let construct = self.apply("construct_pair", &[("a", a), ("b", b)], &[])?; + items.push(self.built(scope, item_expr, &[k, v], construct)?); + cursor = Some(rest); + } + self.cons_chain(scope, expr, &items) + } + SemanticTerm::SetLiteral { + element: _, + elements, + } => { + let mut items = Vec::new(); + let mut cursor = expr; + for element in elements { + let (item_expr, rest) = self.chain(cursor)?; + items.push(self.prove(element, item_expr, scope)?); + cursor = rest; + } + self.cons_chain(scope, expr, &items) + } + SemanticTerm::GraphLiteral { + node: _, + nodes, + edges, + } => { + let mut items = Vec::new(); + let mut cursor = expr; + for source in nodes { + let (item_expr, rest) = self.chain(cursor)?; + let operand_exprs = self.operand_exprs(item_expr)?; + let source_pf = self.prove(source, &operand_exprs[0], scope)?; + let mut targets = Vec::new(); + let mut target_cursor = &operand_exprs[1]; + for SemanticEdge { + source: from, + target, + } in edges + { + if from == source { + let (target_expr, target_rest) = self.chain(target_cursor)?; + targets.push(self.prove(target, target_expr, scope)?); + target_cursor = target_rest; + } + } + let chain = self.cons_chain(scope, &operand_exprs[1], &targets)?; + let a = self.value_of(source_pf)?; + let b = self.value_of(chain)?; + let construct = self.apply("construct_pair", &[("a", a), ("b", b)], &[])?; + items.push(self.built(scope, item_expr, &[source_pf, chain], construct)?); + cursor = rest; + } + self.cons_chain(scope, expr, &items) + } + SemanticTerm::CheckedApply { + model: _, + type_arguments: _, + arguments: _, + checks: _, + } => Err( + "a checked model application is certified only through its elaboration".to_owned(), + ), + SemanticTerm::Lambda { + parameters: _, + captures: _, + body: _, + } + | SemanticTerm::FunctionRef { + function: _, + type_arguments: _, + } => Err("a function value outside an argument or application position".to_owned()), + SemanticTerm::Eq { left: _, right: _ } + | SemanticTerm::Le { left: _, right: _ } + | SemanticTerm::Lt { left: _, right: _ } + | SemanticTerm::PropAnd { left: _, right: _ } + | SemanticTerm::Implies { + premise: _, + conclusion: _, + } + | SemanticTerm::Iff { left: _, right: _ } + | SemanticTerm::Forall { binder: _, body: _ } => Err(format!( + "the formal-only construct `{}` has no realization", + super::super::super::eligibility::term_key(term) + )), + } + } + + /// The proof of a record of closed type `ty`: a build of its fields in + /// declaration order. + pub(super) fn record_proof( + &mut self, + ty: &SemanticType, + fields: &[crate::ir::semantic::SemanticAssignment], + expr: &'a Expr, + scope: &Scope, + ) -> Result { + let shape = self.gen.source.document(ty)?; + let operand_exprs = self.operand_exprs(expr)?; + let mut proofs = Vec::new(); + for (position, field) in shape.field_names.iter().enumerate() { + let value = &fields + .iter() + .find(|assignment| &assignment.field == field) + .ok_or_else(|| format!("field `{field}` is not assigned"))? + .value; + let child = operand_exprs + .get(position) + .ok_or("a record lowered to fewer operands than fields")?; + proofs.push(self.prove(value, child, scope)?); + } + let construct = self.construct_adt(0, &proofs)?; + self.built(scope, expr, &proofs, construct) + } + + // --- Expression plumbing ---------------------------------------------- + + /// The operands of a build or a primitive. + fn operand_exprs(&self, expr: &'a Expr) -> Result<&'a [Expr], String> { + match expr { + Expr::Build { operands, .. } => Ok(operands), + _ => Err("the lowering of a construction is not a build".to_owned()), + } + } + + /// The head and tail of a cons build in a chain. + fn chain(&self, expr: &'a Expr) -> Result<(&'a Expr, &'a Expr), String> { + match expr { + Expr::Build { + shape: Shape::Cons, + operands, + .. + } if operands.len() == 2 => Ok((&operands[0], &operands[1])), + _ => Err("a literal collection lowered to something other than a cons chain".to_owned()), + } + } + + /// The proof of a literal collection: a cons chain of its items ending in + /// a nil, over the expression `expr` the lowering gave it. + fn cons_chain(&mut self, scope: &Scope, expr: &'a Expr, items: &[Pf]) -> Result { + let mut spine: Vec<&'a Expr> = Vec::new(); + let mut cursor = expr; + for _ in items { + spine.push(cursor); + cursor = self.chain(cursor)?.1; + } + let nil = self.construct_plain("construct_nil")?; + let mut out = self.built(scope, cursor, &[], nil)?; + for (item, node) in items.iter().zip(spine).rev() { + let h = self.value_of(*item)?; + let t = self.list_payload(self.value_of(out)?)?; + let construct = self.apply("construct_cons", &[("h", h), ("t", t)], &[])?; + out = self.built(scope, node, &[*item, out], construct)?; + } + Ok(out) + } + + /// The list a `Value.list` wraps. + fn list_payload(&mut self, value: Term) -> Result { + let (head, args) = self.b.spine(value); + let is_list = matches!(self.b.node(head), + crate::ir::core::CoreNode::Const { n, .. } if n.ends_with("TargetSyntax.Value.list")); + if is_list && args.len() == 1 { + Ok(args[0]) + } else { + Err(format!( + "a list value {} is not a Value.list", + self.b.show(value) + )) + } + } + + /// `construct_cons` at the value of `head` and the payload of the + /// encoding of the list `tail_src`. + fn construct_cons( + &mut self, + ty: &SemanticType, + head: Pf, + tail_src: Term, + ) -> Result { + let element = element_of(ty)?; + let alpha = self.lean_ty(&element)?; + let e = self.enc(&element)?; + let h = self.value_of(head)?; + let value = self.value_ty(); + let map = self.b.konst_at( + "List.map", + vec![crate::ir::core::CoreLevel::Zero, crate::ir::core::CoreLevel::Zero], + ); + let t = self.b.apps(map, &[alpha, value, e, tail_src]); + self.apply("construct_cons", &[("h", h), ("t", t)], &[]) + } + + /// `construct_adt` at the constructor `index` and the operands' values. + fn construct_adt(&mut self, index: u64, proofs: &[Pf]) -> Result { + let values = proofs + .iter() + .map(|pf| self.value_of(*pf)) + .collect::, _>>()?; + let ty = self.value_ty(); + let list = self.b.list(ty, &values); + let k = self.nat_u(index); + self.apply("construct_adt", &[("k", k), ("fs", list)], &[]) + } + + /// The projection of `field` out of `value`, a document of type `ty`. + pub(super) fn project_term( + &mut self, + ty: &SemanticType, + field: &str, + value: Term, + ) -> Result { + let (member, arguments) = match ty { + SemanticType::Named { member, arguments } => (member, arguments), + _ => return Err("a projection from a non-document".to_owned()), + }; + let owner = self.document_name(member)?; + let head = self.b.konst(&format!("{owner}.{field}")); + let mut all = Vec::new(); + for argument in arguments { + all.push(self.lean_ty(argument)?); + } + all.push(value); + Ok(self.b.apps(head, &all)) + } + + /// A primitive over two natural operands, realized by `rule`. + fn binary_prim( + &mut self, + left: &SemanticTerm, + right: &SemanticTerm, + expr: &'a Expr, + scope: &Scope, + rule: impl FnOnce(&mut Self, Term, Term) -> Result, + ) -> Result { + let operand_exprs = match expr { + Expr::Prim { operands, .. } if operands.len() == 2 => operands, + _ => return Err("the lowering of an arithmetic term is not a primitive".to_owned()), + }; + let l = self.prove(left, &operand_exprs[0], scope)?; + let r = self.prove(right, &operand_exprs[1], scope)?; + let list = self.operands(scope.env, &[l, r])?; + let a = self.src(left, scope)?; + let b = self.src(right, scope)?; + let lemma = rule(self, a, b)?; + let (p, env) = (self.prog, scope.env); + self.apply( + "conv_prim", + &[("p", p), ("env", env)], + &[ + Given::Proof(list.proof, list.ty), + Given::Proof(lemma.proof, lemma.ty), + ], + ) + } + + /// A conditional: `conv_cond` with the predicate and value motives that + /// select among the branches. + fn conditional( + &mut self, + whole: &SemanticTerm, + condition: &SemanticTerm, + then_value: &SemanticTerm, + else_value: &SemanticTerm, + exprs: (&'a Expr, &'a Expr, &'a Expr), + scope: &Scope, + ) -> Result { + let result_ty = self.infer(whole, scope)?; + self.conditional_of(Selection::If(result_ty), condition, then_value, else_value, exprs, scope) + } + + /// A conditional whose value is selected as `kind` says. + fn conditional_of( + &mut self, + kind: Selection, + condition: &SemanticTerm, + then_value: &SemanticTerm, + else_value: &SemanticTerm, + exprs: (&'a Expr, &'a Expr, &'a Expr), + scope: &Scope, + ) -> Result { + let (p, env) = (self.prog, scope.env); + let tested = self.prove(condition, exprs.0, scope)?; + let then_pf = self.prove(then_value, exprs.1, scope)?; + let else_pf = self.prove(else_value, exprs.2, scope)?; + let condition_src = self.src(condition, scope)?; + let bool = self.b.konst("Bool"); + let guard = self.b.free(); + let then_fits = self.fits_of(then_pf)?; + let else_fits = self.fits_of(else_pf)?; + let fits_body = self.ite(bool, guard, then_fits, else_fits); + let predicate = self.b.lam_over("__b", EXPLICIT, bool, guard, fits_body); + let encoded = match &kind { + Selection::If(result_ty) => { + let lean = self.lean_ty(result_ty)?; + let encoder = self.enc(result_ty)?; + let then_src = self.src(then_value, scope)?; + let else_src = self.src(else_value, scope)?; + let selected = self.ite(lean, guard, then_src, else_src); + self.b.app(encoder, selected) + } + Selection::And => { + let right = self.src(then_value, scope)?; + let combined = self.and(guard, right); + let ctor = self.ts("Value.bool"); + self.b.app(ctor, combined) + } + Selection::Or => { + let right = self.src(else_value, scope)?; + let head = self.b.konst("Bool.or"); + let combined = self.b.apps(head, &[guard, right]); + let ctor = self.ts("Value.bool"); + self.b.app(ctor, combined) + } + }; + let value_fn = self.b.lam_over("__b", EXPLICIT, bool, guard, encoded); + let tru = self.tru(); + let fls = self.fls(); + let then_branch = self.guarded(condition_src, tru, then_pf)?; + let else_branch = self.guarded(condition_src, fls, else_pf)?; + self.apply( + "conv_cond", + &[("p", p), ("env", env), ("P", predicate), ("V", value_fn), ("b", condition_src)], + &[ + Given::Proof(tested.proof, tested.ty), + Given::Proof(then_branch.0, then_branch.1), + Given::Proof(else_branch.0, else_branch.1), + ], + ) + } + + /// `fun (h : cond = value) => proof`, with its type. + fn guarded(&mut self, condition: Term, value: Term, pf: Pf) -> Result<(Term, Term), String> { + let bool = self.b.konst("Bool"); + let ty = self.eq(bool, condition, value); + let hypothesis = self.b.free(); + let proof = self.b.lam_over("_", EXPLICIT, ty, hypothesis, pf.proof); + let lifted = self.b.lift(pf.ty, 1, 0); + let pi = self.b.pi("_", EXPLICIT, ty, lifted); + Ok((proof, pi)) + } + + // --- Let ---------------------------------------------------------------- + + fn let_proof( + &mut self, + binder: &crate::ir::semantic::SemanticParameter, + value: &SemanticTerm, + body: &SemanticTerm, + expr: &'a Expr, + scope: &Scope, + ) -> Result { + let (number, ty, bound_expr, body_expr) = match expr { + Expr::Let { + name, + ty, + bound, + body, + } => (*name, ty, bound.as_ref(), body.as_ref()), + _ => return Err("the lowering of a let is not a let".to_owned()), + }; + let (local, generic) = self.gen.let_local(binder, &scope.ctx); + let local_ty = local.ty.clone(); + let lean = self.lean_ty(&local_ty)?; + let bound = self.prove(value, bound_expr, scope)?; + let bound_src = self.src(value, scope)?; + let free = self.b.free(); + let enc = self.enc(&local_ty)?; + let encoded = self.b.app(enc, free); + let name = self.nat_u(number); + let value_ty = self.value_ty(); + let nat = self.b.konst("Nat"); + let entry = self.b.pair(nat, value_ty, name, encoded); + let entry_ty = self.b.pair_ty(nat, value_ty); + let cons = self.b.konst0("List.cons"); + let env = self.b.apps(cons, &[entry_ty, entry, scope.env]); + let inner = self.bind(scope, vec![(local, generic)], &[free], env); + let rest = self.prove(body, body_expr, &inner)?; + let (p, outer) = (self.prog, scope.env); + let target = self.ty_term(ty); + let applied = self.apply( + "conv_let", + &[("p", p), ("env", outer), ("t", target)], + &[ + Given::Proof(bound.proof, bound.ty), + Given::Proof(rest.proof, rest.ty), + ], + )?; + let label = super::super::identifier(&binder.name); + let proof = self.b.let_over(&label, lean, bound_src, free, applied.proof); + let ty = self.b.let_over(&label, lean, bound_src, free, applied.ty); + Ok(Pf { proof, ty }) + } + +} diff --git a/crates/lexlean/src/production/certificate/data_a/rows.rs b/crates/lexlean/src/production/certificate/data_a/rows.rs new file mode 100644 index 000000000..decd796e5 --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/rows.rs @@ -0,0 +1,595 @@ +//! The rows of certificate A: a width predicate and a relation theorem per +//! function of the closure, then `denote` and `root` (SPEC.md §17.17.4). + +use std::collections::BTreeMap; + +use super::super::{ + function_type, identifier, CLocal, Ctx, Hypotheses, Recursion, Subject, +}; +use super::{CompTerms, LocalTerm, Pf, RowKind, Scope, Writer, EXPLICIT}; +use crate::calculus::Expr; +use crate::ir::semantic::{MemberRef, SemanticParameter, SemanticTerm, SemanticType}; +use crate::production::kernel::{Given, Term}; +use crate::production::source::{Local, Site}; + +/// A binder of a row: its name, type, and the free variable standing for it. +#[derive(Clone)] +pub(super) struct Binder { + pub(super) name: String, + pub(super) ty: Term, + pub(super) free: Term, +} + +/// The binders, arguments, and statement of one function's relation. +#[derive(Clone)] +pub(super) struct Sig { + pub(super) fits_binders: Vec, + pub(super) rel_binders: Vec, + /// The width predicate applied to its binders. + pub(super) fits_applied: Term, + /// The encoded argument list the function is called with. + pub(super) arguments: Term, + /// The encoded denotation of the call. + pub(super) value: Term, + pub(super) rel_params: Vec, + /// `FunRel __prog index arguments (Rel fits value)`. + pub(super) statement: Term, +} + +/// The terms of the locals of a function: a free variable for each, and the +/// companions of a function-typed one, with the binders they make. +pub(super) struct LocalSet { + pub(super) terms: Vec, + pub(super) fits: Vec, + pub(super) rel: Vec, +} + +impl<'a> Writer<'_, 'a> { + /// `List.nil (Prod Nat Value)`: the environment of no bindings. + pub(super) fn empty_env(&mut self) -> Term { + let nat = self.b.konst("Nat"); + let value = self.value_ty(); + let entry = self.b.pair_ty(nat, value); + let nil = self.b.konst0("List.nil"); + self.b.app(nil, entry) + } + + /// Free variables for `locals`, with the binders of the width predicate + /// and of the relation they make: a function-typed local brings the width + /// predicate, index, captured values, and relation hypothesis of the + /// closure it is bound to. + pub(super) fn local_set(&mut self, index: u64, locals: &[CLocal]) -> Result { + let mut terms = Vec::new(); + let mut fits = Vec::new(); + let mut rel = Vec::new(); + for local in locals { + let name = identifier(&local.local.name); + let ty = self.lean_ty(&local.local.ty)?; + let value = self.b.free(); + let binder = Binder { + name: name.clone(), + ty, + free: value, + }; + fits.push(binder.clone()); + rel.push(binder); + let companion = match function_type(&local.local.ty) { + None => None, + Some((parameters, codomain)) => { + let (parameters, codomain) = (parameters.to_vec(), codomain.clone()); + let mut domain = Vec::new(); + for parameter in ¶meters { + domain.push(self.lean_ty(parameter)?); + } + let bool = self.b.konst("Bool"); + let mut fits_ty = bool; + for ty in domain.iter().rev() { + fits_ty = self.b.arrow(*ty, fits_ty); + } + let ff = self.b.free(); + let fi = self.b.free(); + let fc = self.b.free(); + let fh = self.b.free(); + let nat = self.b.konst("Nat"); + let value_ty = self.value_ty(); + let captures_ty = self.b.list_ty(value_ty); + // ∀ p…, FunRel __prog fi (captures ++ [enc p…]) (Rel (ff p…) (enc cod (x p…))) + let ps: Vec = domain.iter().map(|_| self.b.free()).collect(); + let mut encoded = Vec::new(); + for (parameter, p) in parameters.iter().zip(&ps) { + let enc = self.enc(parameter)?; + encoded.push(self.b.app(enc, *p)); + } + let list = self.b.list(value_ty, &encoded); + let append = self.sem("LexLeanRuntime.append"); + let instance = self.sem("LexLeanRuntime.instAppendableList"); + let instance = self.b.app(instance, value_ty); + let arguments = self.b.apps(append, &[captures_ty, instance, fc, list]); + let fits_applied = self.b.apps(ff, &ps); + let applied = self.b.apps(value, &ps); + let codomain_enc = self.enc(&codomain)?; + let result = self.b.app(codomain_enc, applied); + let rel_value = self.rel(fits_applied, result); + let fun_rel = self.lp("FunRel"); + let prog = self.prog; + let mut relation_ty = self.b.apps(fun_rel, &[prog, fi, arguments, rel_value]); + for (position, p) in ps.iter().enumerate().rev() { + relation_ty = self.b.pi_over( + &format!("__p{position}"), + EXPLICIT, + domain[position], + *p, + relation_ty, + ); + } + let _ = index; + fits.push(Binder { + name: "__ff".to_owned(), + ty: fits_ty, + free: ff, + }); + rel.push(Binder { + name: "__ff".to_owned(), + ty: fits_ty, + free: ff, + }); + rel.push(Binder { + name: "__fi".to_owned(), + ty: nat, + free: fi, + }); + rel.push(Binder { + name: "__fc".to_owned(), + ty: captures_ty, + free: fc, + }); + rel.push(Binder { + name: "__fh".to_owned(), + ty: relation_ty, + free: fh, + }); + Some(CompTerms { + relation_ty, + fits: ff, + index: fi, + captures: fc, + relation: fh, + }) + } + }; + terms.push(LocalTerm { value, companion }); + } + Ok(LocalSet { terms, fits, rel }) + } + + /// The evaluation environment of a function run on its locals: its + /// parameters bound to the locals' encodings, as `bindAll` binds them. + pub(super) fn entry_env( + &mut self, + index: u64, + locals: &[CLocal], + terms: &[LocalTerm], + ) -> Result { + let parameters = self + .gen + .program + .functions + .get(index as usize) + .ok_or_else(|| format!("the program has no function {index}"))? + .parameters + .clone(); + if parameters.len() != locals.len() { + return Err(format!( + "function {index} takes {} parameters, the certificate binds {}", + parameters.len(), + locals.len() + )); + } + let mut values = Vec::new(); + for (local, term) in locals.iter().zip(terms) { + values.push(self.encoded_local(local, *term)?); + } + let empty = self.empty_env(); + self.extend_env(empty, ¶meters, &values) + } + + /// The signature of function `index` over `locals`, whose denotation + /// applied to them is `denotation`. + pub(super) fn signature( + &mut self, + index: u64, + locals: &[CLocal], + set: &LocalSet, + result: &SemanticType, + denotation: Term, + ) -> Result { + let mut fits_args = Vec::new(); + let mut arguments = Vec::new(); + for ((local, terms), _) in locals.iter().zip(&set.terms).zip(0..) { + fits_args.push(terms.value); + if let Some(companion) = terms.companion { + fits_args.push(companion.fits); + } + arguments.push(self.encoded_local(local, *terms)?); + } + let fits_head = self.own(&format!("__fits_{index}")); + let fits_applied = self.b.apps(fits_head, &fits_args); + let value_ty = self.value_ty(); + let arguments = self.b.list(value_ty, &arguments); + let encoder = self.enc(result)?; + let value = self.b.app(encoder, denotation); + let rel_value = self.rel(fits_applied, value); + let fun_rel = self.lp("FunRel"); + let prog = self.prog; + let i = self.nat_u(index); + let statement = self.b.apps(fun_rel, &[prog, i, arguments, rel_value]); + Ok(Sig { + fits_binders: set.fits.clone(), + rel_binders: set.rel.clone(), + fits_applied, + arguments, + value, + rel_params: set.rel.iter().map(|binder| binder.free).collect(), + statement, + }) + } + + /// `fun binders => body`. + pub(super) fn lambdas(&mut self, binders: &[Binder], body: Term) -> Term { + let mut out = body; + for binder in binders.iter().rev() { + out = self + .b + .lam_over(&binder.name, EXPLICIT, binder.ty, binder.free, out); + } + out + } + + /// `(binders) → body`. + pub(super) fn pis(&mut self, binders: &[Binder], body: Term) -> Term { + let mut out = body; + for binder in binders.iter().rev() { + out = self + .b + .pi_over(&binder.name, EXPLICIT, binder.ty, binder.free, out); + } + out + } + + /// `funRel_intro rfl rfl body`: the relation of function `index`, whose + /// body is derived by `body` in the environment `env`. + pub(super) fn relation_proof( + &mut self, + index: u64, + sig: &Sig, + env: Term, + body: Pf, + ) -> Result { + let program = self.gen.program; + let function = program + .functions + .get(index as usize) + .ok_or_else(|| format!("the program has no function {index}"))?; + let function = self.function_term(function)?; + let (_, parts) = self.b.spine(sig.statement); + let observation = parts.get(3).copied().ok_or("a statement with no observation")?; + let p = self.prog; + let fi = self.nat_u(index); + self.apply( + "funRel_intro", + &[ + ("p", p), + ("fi", fi), + ("args", sig.arguments), + ("o", observation), + ("fn", function), + ("env", env), + ], + &[ + Given::Refl, + Given::Refl, + Given::Proof(body.proof, body.ty), + ], + ) + } + + /// The width predicate row and relation row of function `index`. + pub(super) fn function_rows( + &mut self, + index: u64, + sig: &Sig, + body: Pf, + env: Term, + ) -> Result<(), String> { + let bool = self.b.konst("Bool"); + let fits = self.fits_of(body)?; + let fits_value = self.lambdas(&sig.fits_binders, fits); + let fits_ty = self.pis(&sig.fits_binders, bool); + self.push_row( + &format!("__fits_{index}"), + RowKind::Definition { compiled: true }, + fits_ty, + fits_value, + )?; + let proof = self.relation_proof(index, sig, env, body)?; + let rel_value = self.lambdas(&sig.rel_binders, proof.proof); + let rel_ty = self.pis(&sig.rel_binders, sig.statement); + self.push_row(&format!("__rel_{index}"), RowKind::Theorem, rel_ty, rel_value) + } + + /// One strongly connected component of the relation graph. + pub(super) fn component( + &mut self, + component: &[u64], + graph: &BTreeMap>, + ) -> Result<(), String> { + let recursive = component.len() > 1 + || graph + .get(&component[0]) + .is_some_and(|edges| edges.contains(&component[0])); + for index in component { + let subject = self.gen.subject_of(*index)?; + match subject { + Subject::Definition { + module, + name, + type_arguments, + locals, + result, + body, + recursion, + site, + type_parameters, + } => { + let clocals = Gen::scope(&locals, ""); + match recursion { + Recursion::None => { + if recursive { + return Err(format!( + "`{module}.{name}` recurses without recursion evidence" + )); + } + let set = self.local_set(*index, &clocals)?; + let denotation = self.denotation(&module, &name, &type_arguments, &set)?; + let sig = self.signature(*index, &clocals, &set, &result, denotation)?; + self.sigs.insert(*index, sig.clone()); + let env = self.entry_env(*index, &clocals, &set.terms)?; + let scope = Scope { + ctx: Ctx::new( + site, + type_parameters, + clocals, + Hypotheses::new(), + *index, + ), + terms: set.terms.clone(), + env, + }; + let program = self.gen.program; + let body_expr = &program.functions[*index as usize].body; + let pf = self.prove(body, body_expr, &scope)?; + self.function_rows(*index, &sig, pf, env)?; + } + Recursion::Structural { .. } | Recursion::WellFounded { .. } => { + return Err(format!( + "`{module}.{name}` is recursive, which the data certificate does not yet write" + )); + } + } + } + Subject::Instance { + module, + name, + class, + fields, + } => { + if recursive { + return Err(format!("instance `{module}.{name}` is recursive")); + } + let set = self.local_set(*index, &[])?; + let lean_name = self.gen.source.lean_name( + &module, + &MemberRef { + module: Some(module.clone()), + name: name.clone(), + }, + ); + let denotation = self.b.konst(&lean_name); + let sig = self.signature(*index, &[], &set, &class, denotation)?; + self.sigs.insert(*index, sig.clone()); + let env = self.empty_env(); + let scope = Scope { + ctx: Ctx::new( + Site { + module: module.clone(), + substitution: BTreeMap::new(), + }, + Vec::new(), + Vec::new(), + Hypotheses::new(), + *index, + ), + terms: Vec::new(), + env, + }; + let program = self.gen.program; + let body_expr = &program.functions[*index as usize].body; + let pf = self.record(&class, fields, body_expr, &scope)?; + self.function_rows(*index, &sig, pf, env)?; + } + Subject::Lambda { + site: _, + captures: _, + parameters: _, + body, + hypotheses: _, + type_parameters: _, + } => { + if recursive { + return Err(format!("lambda function {index} is recursive")); + } + let ctx = self.gen.lambda_ctx(*index)?; + let clocals = ctx.locals.clone(); + let set = self.local_set(*index, &clocals)?; + let result = self + .gen + .source + .infer(body, &ctx.sources(), &ctx.site)?; + let env = self.entry_env(*index, &clocals, &set.terms)?; + let scope = Scope { + ctx, + terms: set.terms.clone(), + env, + }; + let denotation = self.src(body, &scope)?; + let sig = self.signature(*index, &clocals, &set, &result, denotation)?; + self.sigs.insert(*index, sig.clone()); + let program = self.gen.program; + let body_expr = &program.functions[*index as usize].body; + let pf = self.prove(body, body_expr, &scope)?; + self.function_rows(*index, &sig, pf, env)?; + } + } + } + Ok(()) + } + + /// The denotation of a definition instance applied to its locals. + fn denotation( + &mut self, + module: &str, + name: &str, + type_arguments: &[SemanticType], + set: &LocalSet, + ) -> Result { + let lean_name = self.gen.source.lean_name( + module, + &MemberRef { + module: Some(module.to_owned()), + name: name.to_owned(), + }, + ); + let head = self.b.konst(&lean_name); + let mut args = Vec::new(); + for argument in type_arguments { + args.push(self.lean_ty(argument)?); + } + args.extend(set.terms.iter().map(|terms| terms.value)); + Ok(self.b.apps(head, &args)) + } + + /// The encoders of every document type: none, until a program has one. + pub(super) fn encoders(&mut self) -> Result<(), String> { + if self.gen.layout.adts.is_empty() { + return Ok(()); + } + Err("document types".to_owned()) + } + + /// `denote`, `root`, and the boundary. + pub(super) fn root( + &mut self, + root_module: &str, + root_name: &str, + report: &super::super::RootReport, + ) -> Result<(), String> { + let root_lean = self.gen.source.lean_name( + root_module, + &MemberRef { + module: Some(root_module.to_owned()), + name: root_name.to_owned(), + }, + ); + if root_lean != report.root { + return Err(format!( + "the certificate root `{root_lean}` is not the report's `{}`", + report.root + )); + } + let (parameters, result) = self.gen.source.signature(root_module, root_name, &[])?; + let locals: Vec<(Local, SemanticType)> = parameters + .into_iter() + .map(|SemanticParameter { name, r#type }| { + ( + Local { + name, + ty: r#type.clone(), + }, + r#type, + ) + }) + .collect(); + let clocals = Gen::scope(&locals, ""); + let set = self.local_set(0, &clocals)?; + let head = self.b.konst(&root_lean); + let values: Vec = set.terms.iter().map(|terms| terms.value).collect(); + let denotation = self.b.apps(head, &values); + let sig = self.signature(0, &clocals, &set, &result, denotation)?; + // The observation as a definition, so the differential evaluator can + // compute the right-hand side the theorem states: `Rel` unfolded to the + // lazy `cond`. + let obs = self.lp("Obs"); + let wrap = self.lp("Obs.value"); + let overflow = self.lp("Obs.overflow"); + let valued = self.b.app(wrap, sig.value); + let cond = self.b.konst_at( + "cond", + vec![crate::ir::core::CoreLevel::Succ(Box::new( + crate::ir::core::CoreLevel::Zero, + ))], + ); + let observation = self.b.apps(cond, &[obs, sig.fits_applied, valued, overflow]); + let denote_value = self.lambdas(&sig.fits_binders, observation); + let denote_ty = self.pis(&sig.fits_binders, obs); + self.push_row( + "denote", + RowKind::Definition { compiled: true }, + denote_ty, + denote_value, + )?; + let rel0 = self.call_relation_at(0, &sig)?; + let run_conv = self.lp("RunConv"); + let prog = self.prog; + let entry = self.nat_u(0); + let rel = self.rel(sig.fits_applied, sig.value); + let root_ty = self.b.apps(run_conv, &[prog, entry, sig.arguments, rel]); + let root = self.apply( + "run_of_funRel", + &[], + &[Given::Proof(rel0.proof, rel0.ty)], + )?; + let root_value = self.lambdas(&sig.rel_binders, root.proof); + let root_pi = self.pis(&sig.rel_binders, root_ty); + self.push_row("root", RowKind::Theorem, root_pi, root_value)?; + let parameter_types: Vec = locals.iter().map(|(_, ty)| ty.clone()).collect(); + self.boundary(&sig, ¶meter_types) + } + + /// The relation of function `index` at its own binders. + fn call_relation_at(&mut self, index: u64, sig: &Sig) -> Result { + let head = self.own(&format!("__rel_{index}")); + let proof = self.b.apps(head, &sig.rel_params); + Ok(Pf { + proof, + ty: sig.statement, + }) + } + + /// Subject lookups for tools that read a function's definition. + pub(super) fn is_term_var(term: &SemanticTerm) -> bool { + matches!(term, SemanticTerm::Var { name: _ }) + } + + /// A record (instance or document value) of closed type `ty`. + pub(super) fn record( + &mut self, + ty: &SemanticType, + fields: &[crate::ir::semantic::SemanticAssignment], + expr: &'a Expr, + scope: &Scope, + ) -> Result { + self.record_proof(ty, fields, expr, scope) + } +} + +use super::super::Gen; diff --git a/crates/lexlean/src/production/certificate/data_a/src.rs b/crates/lexlean/src/production/certificate/data_a/src.rs new file mode 100644 index 000000000..00d33206b --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/src.rs @@ -0,0 +1,1013 @@ +//! The Lean denotation of a source term as a kernel term: what the generated +//! module states the term to be, fully elaborated (SPEC.md §17.17). +//! +//! A term the module writes as a call is a call of the constant, with the type +//! arguments the source gives; a primitive is the runtime function of the +//! term's module at the instance the elaborator selects for the operand +//! types; a `match` is the constructor-by-constructor eliminator of the +//! scrutinee's type, which the module's own matcher unfolds to. + +use super::super::{element_of, integer_lean, primitive_name}; +use super::{LocalTerm, Scope, Writer}; +use crate::ir::core::CoreLevel; +use crate::ir::semantic::{ + MemberRef, SemanticAssignment, SemanticBranch, SemanticEdge, SemanticMapEntry, + SemanticPrimitive, SemanticTerm, SemanticType, +}; +use crate::production::kernel::Term; +use crate::production::source::{Constructor, Local, Site}; + +impl Writer<'_, '_> { + // --- Scopes ----------------------------------------------------------- + + /// `scope` with `locals` bound to the free variables `frees` in order. + pub(super) fn bind( + &self, + scope: &Scope, + locals: Vec<(Local, SemanticType)>, + frees: &[Term], + env: Term, + ) -> Scope { + let mut out = scope.clone(); + out.ctx = scope.ctx.with(locals); + out.terms.extend(frees.iter().map(|free| LocalTerm { + value: *free, + companion: None, + })); + out.env = env; + out + } + + // --- Names ------------------------------------------------------------ + + /// The unquoted Lean name of a member read at `site`. + pub(super) fn member_name(&self, member: &MemberRef, site: &Site) -> Result { + if member.module.is_none() { + if member.name == "Result.error" { + return Ok("Except.error".to_owned()); + } + if member.name == "Result.ok" { + return Ok("Except.ok".to_owned()); + } + let owner = member.name.rsplit_once('.').map(|(owner, _)| owner); + if super::super::BUILTIN_OWNERS + .iter() + .any(|(name, _)| Some(*name) == owner) + { + return Ok(member.name.clone()); + } + } + let module = crate::production::source::Source::module_of(member, site); + Ok(format!( + "{}.{}", + self.gen.source.lean_module(&module)?, + member.name + )) + } + + fn member_const(&mut self, member: &MemberRef, site: &Site) -> Result { + let name = self.member_name(member, site)?; + Ok(self.b.konst(&name)) + } + + /// A runtime function of the module `site` is read in. + fn runtime(&mut self, site: &Site, namespace: &str, name: &str) -> Result { + let module = self.gen.source.lean_module(&site.module)?; + Ok(self.b.konst(&format!("{module}.{namespace}.{name}"))) + } + + fn runtime_name(&self, site: &Site, namespace: &str, name: &str) -> Result { + let module = self.gen.source.lean_module(&site.module)?; + Ok(format!("{module}.{namespace}.{name}")) + } + + // --- Instances -------------------------------------------------------- + + fn fixed_name(ty: &SemanticType) -> Result<&'static str, String> { + Ok(match ty { + SemanticType::Int8 => "Int8", + SemanticType::Int16 => "Int16", + SemanticType::Int32 => "Int32", + SemanticType::Int64 => "Int64", + SemanticType::UInt8 => "UInt8", + SemanticType::UInt16 => "UInt16", + SemanticType::UInt32 => "UInt32", + SemanticType::UInt64 => "UInt64", + SemanticType::Type + | SemanticType::Parameter { name: _ } + | SemanticType::Nat + | SemanticType::Bool + | SemanticType::Prop + | SemanticType::Unit + | SemanticType::Int + | SemanticType::String + | SemanticType::Bytes + | SemanticType::Ordering + | SemanticType::Option { value: _ } + | SemanticType::Result { ok: _, error: _ } + | SemanticType::List { element: _ } + | SemanticType::Named { + member: _, + arguments: _, + } + | SemanticType::Product { left: _, right: _ } + | SemanticType::Function { + parameters: _, + result: _, + } + | SemanticType::Map { key: _, value: _ } + | SemanticType::Set { element: _ } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure => { + return Err("a fixed-width operation at a non-fixed type".to_owned()); + } + }) + } + + fn fixed_instance(&mut self, site: &Site, ty: &SemanticType) -> Result { + let name = Self::fixed_name(ty)?; + self.runtime(site, "LexLeanRuntime", &format!("instFixed{name}")) + } + + /// The `ToMathInt` instance of an operand of `checkedConvert`. + fn to_math_int(&mut self, site: &Site, ty: &SemanticType) -> Result { + match ty { + SemanticType::Int => self.runtime(site, "LexLeanRuntime", "instToMathIntInt"), + _ => { + let name = Self::fixed_name(ty)?; + let to = self.runtime(site, "LexLeanRuntime", "Fixed.toToMathInt")?; + let fixed = self.lean_ty(ty)?; + let instance = + self.runtime(site, "LexLeanRuntime", &format!("instFixed{name}"))?; + Ok(self.b.apps(to, &[fixed, instance])) + } + } + } + + /// The `BEq` instance of an operand of `equal`. + fn beq_instance(&mut self, ty: &SemanticType) -> Result { + let name = match ty { + SemanticType::Nat => "Nat", + SemanticType::Bool => "Bool", + SemanticType::String => "String", + SemanticType::Ordering => "Ordering", + SemanticType::Int8 + | SemanticType::Int16 + | SemanticType::Int32 + | SemanticType::Int64 + | SemanticType::UInt8 + | SemanticType::UInt16 + | SemanticType::UInt32 + | SemanticType::UInt64 => Self::fixed_name(ty)?, + SemanticType::Bytes => return Ok(self.b.konst("ByteArray.instBEq")), + SemanticType::Type + | SemanticType::Parameter { name: _ } + | SemanticType::Prop + | SemanticType::Unit + | SemanticType::Int + | SemanticType::Option { value: _ } + | SemanticType::Result { ok: _, error: _ } + | SemanticType::List { element: _ } + | SemanticType::Named { + member: _, + arguments: _, + } + | SemanticType::Product { left: _, right: _ } + | SemanticType::Function { + parameters: _, + result: _, + } + | SemanticType::Map { key: _, value: _ } + | SemanticType::Set { element: _ } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure => { + return Err("an equality at a non-scalar type".to_owned()); + } + }; + let carrier = self.lean_ty(ty)?; + let decide = self.b.konst(&format!("instDecidableEq{name}")); + let beq = self.b.konst0("instBEqOfDecidableEq"); + Ok(self.b.apps(beq, &[carrier, decide])) + } + + /// The `Decimal` instance of the type a decimal is parsed to or printed + /// from. + fn decimal_instance(&mut self, site: &Site, ty: &SemanticType) -> Result { + if matches!(ty, SemanticType::Int) { + return self.runtime(site, "LexLeanRuntime", "instDecimalInt"); + } + let name = Self::fixed_name(ty)?; + let carrier = self.lean_ty(ty)?; + let head = self.runtime(site, "LexLeanRuntime", "instDecimalOfFixedOfToString")?; + let fixed = self.fixed_instance(site, ty)?; + let to_string = self.b.konst(&format!("instToString{name}")); + Ok(self.b.apps(head, &[carrier, fixed, to_string])) + } + + /// A primitive's runtime call on operands of the given types, as the + /// module elaborates `(LexLeanRuntime.op args : result)`. + #[allow(clippy::too_many_lines)] + pub(super) fn runtime_call( + &mut self, + site: &Site, + operation: SemanticPrimitive, + types: &[SemanticType], + result: &SemanticType, + args: &[Term], + ) -> Result { + let first = types.first(); + let (namespace, name) = primitive_name(operation); + let int64 = SemanticType::Option { + value: Box::new(SemanticType::Int64), + }; + let special = match operation { + SemanticPrimitive::CheckedAdd => Some("checkedAddInt64"), + SemanticPrimitive::CheckedSubtract => Some("checkedSubtractInt64"), + SemanticPrimitive::CheckedMultiply => Some("checkedMultiplyInt64"), + SemanticPrimitive::CheckedNegate => Some("checkedNegateInt64"), + SemanticPrimitive::CheckedQuotient => Some("checkedQuotientInt64"), + _ => None, + }; + if let Some(special) = special { + if *result == int64 { + let head = self.runtime(site, "LexLeanRuntime", special)?; + return Ok(self.b.apps(head, args)); + } + } + let head = self.runtime(site, namespace, name)?; + let mut leading: Vec = Vec::new(); + match operation { + SemanticPrimitive::CheckedAdd + | SemanticPrimitive::CheckedSubtract + | SemanticPrimitive::CheckedMultiply + | SemanticPrimitive::CheckedNegate + | SemanticPrimitive::CheckedQuotient + | SemanticPrimitive::BitAnd + | SemanticPrimitive::BitOr + | SemanticPrimitive::BitXor + | SemanticPrimitive::BitNot + | SemanticPrimitive::ShiftLeft + | SemanticPrimitive::ShiftRight => { + let ty = first.ok_or("a fixed-width operation without an operand")?; + leading.push(self.lean_ty(ty)?); + leading.push(self.fixed_instance(site, ty)?); + } + SemanticPrimitive::CheckedConvert => { + let from = first.ok_or("a conversion without an operand")?; + let to = match result { + SemanticType::Option { value } => value.as_ref(), + _ => return Err("a conversion to a non-option".to_owned()), + }; + leading.push(self.lean_ty(from)?); + leading.push(self.lean_ty(to)?); + leading.push(self.to_math_int(site, from)?); + leading.push(self.fixed_instance(site, to)?); + } + SemanticPrimitive::Subtract + | SemanticPrimitive::Multiply + | SemanticPrimitive::Negate => { + let ty = first.ok_or("an arithmetic operation without an operand")?; + leading.push(self.lean_ty(ty)?); + let instance = match (operation, ty) { + (SemanticPrimitive::Subtract, SemanticType::Nat) => "instSubNat", + (SemanticPrimitive::Subtract, _) => "Int.instSub", + (SemanticPrimitive::Multiply, SemanticType::Nat) => "instMulNat", + (SemanticPrimitive::Multiply, _) => "Int.instMul", + (_, _) => "Int.instNegInt", + }; + leading.push(self.b.konst(instance)); + } + SemanticPrimitive::Quotient | SemanticPrimitive::Remainder => { + let ty = first.ok_or("a division without an operand")?; + leading.push(self.lean_ty(ty)?); + let instance = if matches!(ty, SemanticType::Nat) { + "instQuotientNat" + } else { + "instQuotientInt" + }; + leading.push(self.runtime(site, "LexLeanRuntime", instance)?); + } + SemanticPrimitive::Append + | SemanticPrimitive::Length + | SemanticPrimitive::Slice => { + let ty = first.ok_or("a sequence operation without an operand")?; + leading.push(self.lean_ty(ty)?); + let class = match operation { + SemanticPrimitive::Append => "Appendable", + SemanticPrimitive::Length => "Lengthable", + _ => "Sliceable", + }; + leading.push(self.sequence_instance(site, class, ty)?); + } + SemanticPrimitive::Index => { + let ty = first.ok_or("an index without an operand")?; + leading.push(self.lean_ty(ty)?); + match ty { + SemanticType::Bytes => { + leading.push(self.b.konst("UInt8")); + leading.push(self.runtime( + site, + "LexLeanRuntime", + "instIndexableByteArrayUInt8", + )?); + } + _ => { + let element = self.lean_ty(&element_of(ty)?)?; + leading.push(element); + let head = self.runtime(site, "LexLeanRuntime", "instIndexableList")?; + leading.push(self.b.app(head, element)); + } + } + } + SemanticPrimitive::Equal => { + let ty = first.ok_or("an equality without an operand")?; + leading.push(self.lean_ty(ty)?); + leading.push(self.beq_instance(ty)?); + } + SemanticPrimitive::ParseDecimal => { + let target = match result { + SemanticType::Option { value } => value.as_ref(), + _ => return Err("a decimal parse to a non-option".to_owned()), + }; + leading.push(self.lean_ty(target)?); + leading.push(self.decimal_instance(site, target)?); + } + SemanticPrimitive::FormatDecimal => { + let ty = first.ok_or("a decimal format without an operand")?; + leading.push(self.lean_ty(ty)?); + leading.push(self.decimal_instance(site, ty)?); + } + SemanticPrimitive::Utf8Encode + | SemanticPrimitive::Utf8Decode + | SemanticPrimitive::CompareBytes + | SemanticPrimitive::SplitExact + | SemanticPrimitive::Join => {} + SemanticPrimitive::MapInsert + | SemanticPrimitive::MapRemove + | SemanticPrimitive::MapLookup + | SemanticPrimitive::MapContains + | SemanticPrimitive::MapSize + | SemanticPrimitive::MapKeys + | SemanticPrimitive::MapValues + | SemanticPrimitive::MapEntries + | SemanticPrimitive::MapFold + | SemanticPrimitive::SetInsert + | SemanticPrimitive::SetRemove + | SemanticPrimitive::SetContains + | SemanticPrimitive::SetSize + | SemanticPrimitive::SetElements + | SemanticPrimitive::SetUnion + | SemanticPrimitive::SetIntersection + | SemanticPrimitive::SetDifference + | SemanticPrimitive::SetFold + | SemanticPrimitive::ListFold + | SemanticPrimitive::Iterate + | SemanticPrimitive::IterateUntil + | SemanticPrimitive::GraphSuccessors + | SemanticPrimitive::GraphReachable + | SemanticPrimitive::GraphTopological + | SemanticPrimitive::LessThan => { + return Err(format!( + "{operation:?} is a collection operation, certified through its template" + )); + } + } + // `quotient` and `remainder` take their zero case as a third operand. + let mut all = leading; + all.extend_from_slice(args); + Ok(self.b.apps(head, &all)) + } + + fn sequence_instance( + &mut self, + site: &Site, + class: &str, + ty: &SemanticType, + ) -> Result { + match ty { + SemanticType::Bytes => { + self.runtime(site, "LexLeanRuntime", &format!("inst{class}ByteArray")) + } + SemanticType::String => { + self.runtime(site, "LexLeanRuntime", &format!("inst{class}String")) + } + _ => { + let element = self.lean_ty(&element_of(ty)?)?; + let head = self.runtime(site, "LexLeanRuntime", &format!("inst{class}List"))?; + Ok(self.b.app(head, element)) + } + } + } + + // --- Constructors and eliminators --------------------------------------- + + /// A constructor applied to its arguments, the closed type `ty` being the + /// type it builds. + pub(super) fn constructed( + &mut self, + constructor: &Constructor, + ty: &SemanticType, + member: &MemberRef, + site: &Site, + args: &[Term], + ) -> Result { + Ok(match constructor { + Constructor::Bool(value) => self.boolean(*value), + Constructor::Zero => self.b.konst("Nat.zero"), + Constructor::Succ => { + let head = self.b.konst("Nat.succ"); + self.b.apps(head, args) + } + Constructor::Nil => { + let element = self.lean_ty(&element_of(ty)?)?; + let head = self.b.konst0("List.nil"); + self.b.app(head, element) + } + Constructor::Cons => { + let element = self.lean_ty(&element_of(ty)?)?; + let head = self.b.konst0("List.cons"); + let mut all = vec![element]; + all.extend_from_slice(args); + self.b.apps(head, &all) + } + Constructor::OptionNone | Constructor::OptionSome => { + let value = super::super::option_value(ty)?; + let value = self.lean_ty(&value)?; + let name = if matches!(constructor, Constructor::OptionNone) { + "Option.none" + } else { + "Option.some" + }; + let head = self.b.konst0(name); + let mut all = vec![value]; + all.extend_from_slice(args); + self.b.apps(head, &all) + } + Constructor::Ok | Constructor::Error => { + let (ok, error) = match ty { + SemanticType::Result { ok, error } => (ok.as_ref(), error.as_ref()), + _ => return Err("a result constructor at a non-result type".to_owned()), + }; + let ok = self.lean_ty(ok)?; + let error = self.lean_ty(error)?; + let name = if matches!(constructor, Constructor::Ok) { + "Except.ok" + } else { + "Except.error" + }; + let head = self.b.konst00(name); + let mut all = vec![error, ok]; + all.extend_from_slice(args); + self.b.apps(head, &all) + } + Constructor::Violation(first, second) => { + let bool = self.b.konst("Bool"); + let a = self.boolean(*first); + let b = self.boolean(*second); + self.b.pair(bool, bool, a, b) + } + Constructor::Document { ty: _, index: _ } => { + let head = self.member_const(member, site)?; + let mut all = Vec::new(); + if let SemanticType::Named { + member: _, + arguments, + } = ty + { + for argument in arguments { + all.push(self.lean_ty(argument)?); + } + } + all.extend_from_slice(args); + self.b.apps(head, &all) + } + }) + } + + /// The eliminator of the type of `scrutinee` at the constant motive + /// `result : Type`, applied to the minor premises of its constructors in + /// declaration order. + pub(super) fn cases_on_const( + &mut self, + scrutinee_ty: &SemanticType, + scrutinee: Term, + result: Term, + minors: &[Term], + ) -> Result { + let carrier = self.lean_ty(scrutinee_ty)?; + let lifted = self.b.lift(result, 1, 0); + let motive = self.b.lam("_", super::EXPLICIT, carrier, lifted); + let one = CoreLevel::Succ(Box::new(CoreLevel::Zero)); + self.cases_on(scrutinee_ty, scrutinee, motive, one, minors) + } + + /// The eliminator of the type of `scrutinee` at `motive`, a function of + /// the scrutinee into the sort of level `level`, applied to the minor + /// premises of its constructors in declaration order. + pub(super) fn cases_on( + &mut self, + scrutinee_ty: &SemanticType, + scrutinee: Term, + motive: Term, + level: CoreLevel, + minors: &[Term], + ) -> Result { + let (head, params): (Term, Vec) = match scrutinee_ty { + SemanticType::Bool => (self.b.konst_at("Bool.casesOn", vec![level]), Vec::new()), + SemanticType::Nat => (self.b.konst_at("Nat.casesOn", vec![level]), Vec::new()), + SemanticType::List { element } | SemanticType::Set { element } => { + let element = self.lean_ty(element)?; + ( + self.b + .konst_at("List.casesOn", vec![level, CoreLevel::Zero]), + vec![element], + ) + } + SemanticType::Option { value } => { + let value = self.lean_ty(value)?; + ( + self.b + .konst_at("Option.casesOn", vec![level, CoreLevel::Zero]), + vec![value], + ) + } + SemanticType::Result { ok, error } => { + let error = self.lean_ty(error)?; + let ok = self.lean_ty(ok)?; + ( + self.b.konst_at( + "Except.casesOn", + vec![level, CoreLevel::Zero, CoreLevel::Zero], + ), + vec![error, ok], + ) + } + SemanticType::Named { member, arguments } => { + let owner = self.document_name(member)?; + let mut params = Vec::new(); + for argument in arguments { + params.push(self.lean_ty(argument)?); + } + (self.b.konst_at(&format!("{owner}.casesOn"), vec![level]), params) + } + SemanticType::Type + | SemanticType::Parameter { name: _ } + | SemanticType::Prop + | SemanticType::Unit + | SemanticType::Int + | SemanticType::Int8 + | SemanticType::Int16 + | SemanticType::Int32 + | SemanticType::Int64 + | SemanticType::UInt8 + | SemanticType::UInt16 + | SemanticType::UInt32 + | SemanticType::UInt64 + | SemanticType::String + | SemanticType::Bytes + | SemanticType::Ordering + | SemanticType::Product { left: _, right: _ } + | SemanticType::Function { + parameters: _, + result: _, + } + | SemanticType::Map { key: _, value: _ } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure => { + return Err(format!( + "a match on `{}` has no eliminator here", + self.gen.source.type_text(scrutinee_ty) + )); + } + }; + let mut all = params; + all.push(motive); + all.push(scrutinee); + all.extend_from_slice(minors); + Ok(self.b.apps(head, &all)) + } + + // --- The denotation --------------------------------------------------- + + pub(super) fn src_all(&mut self, terms: &[SemanticTerm], scope: &Scope) -> Result, String> { + terms.iter().map(|term| self.src(term, scope)).collect() + } + + /// The closed type of `term` in `scope`. + pub(super) fn infer(&self, term: &SemanticTerm, scope: &Scope) -> Result { + self.gen + .source + .infer(term, &scope.ctx.sources(), &scope.ctx.site) + } + + /// The Lean denotation of a source term. + #[allow(clippy::too_many_lines)] + pub(super) fn src(&mut self, term: &SemanticTerm, scope: &Scope) -> Result { + let site = scope.ctx.site.clone(); + Ok(match term { + SemanticTerm::Var { name } => scope.lookup(name)?.1.value, + SemanticTerm::Nat { value } => self.nat_lit(value), + SemanticTerm::Integer { + representation, + value, + } => self.int_literal(integer_lean(*representation), value), + SemanticTerm::String { value } => self.b.string(value), + SemanticTerm::Bytes { hex } => self.bytes_term(hex)?, + SemanticTerm::Primitive { + operation, + arguments, + result, + } => { + let result = self.gen.source.close(result, &site); + let types = arguments + .iter() + .map(|argument| self.infer(argument, scope)) + .collect::, _>>()?; + let args = self.src_all(arguments, scope)?; + self.runtime_call(&site, *operation, &types, &result, &args)? + } + SemanticTerm::Bool { value } => self.boolean(*value), + SemanticTerm::Unit => self.b.konst("Unit.unit"), + SemanticTerm::Nil { element } => { + let element = self.gen.source.close(element, &site); + let element = self.lean_ty(&element)?; + let head = self.b.konst0("List.nil"); + self.b.app(head, element) + } + SemanticTerm::Cons { head, tail } => { + let ty = self.infer(term, scope)?; + let element = self.lean_ty(&element_of(&ty)?)?; + let head = self.src(head, scope)?; + let tail = self.src(tail, scope)?; + let cons = self.b.konst0("List.cons"); + self.b.apps(cons, &[element, head, tail]) + } + SemanticTerm::Record { + r#type: _, + type_arguments: _, + fields, + } => { + let ty = self.infer(term, scope)?; + self.record_value(&ty, fields, scope)? + } + SemanticTerm::Constructor { + constructor, + type_arguments, + arguments, + } => { + let ty = self.infer(term, scope)?; + let closed: Vec = type_arguments + .iter() + .map(|argument| self.gen.source.close(argument, &site)) + .collect(); + let (kind, _) = self.gen.source.constructor(constructor, &closed, &site)?; + let args = self.src_all(arguments, scope)?; + self.constructed(&kind, &ty, constructor, &site, &args)? + } + SemanticTerm::InstanceValue { + class: _, + arguments: _, + resolved, + } => self.member_const(resolved, &site)?, + SemanticTerm::Project { value, field } => { + let ty = self.infer(value, scope)?; + let owner = match &ty { + SemanticType::Named { member, arguments } => { + let mut params = Vec::new(); + for argument in arguments { + params.push(self.lean_ty(argument)?); + } + (self.document_name(member)?, params) + } + _ => return Err("a projection from a non-document".to_owned()), + }; + let head = self.b.konst(&format!("{}.{field}", owner.0)); + let value = self.src(value, scope)?; + let mut all = owner.1; + all.push(value); + self.b.apps(head, &all) + } + SemanticTerm::Call { + function, + type_arguments, + arguments, + } => { + let head = self.member_const(function, &site)?; + let mut all = Vec::new(); + for argument in type_arguments { + let closed = self.gen.source.close(argument, &site); + all.push(self.lean_ty(&closed)?); + } + for argument in arguments { + all.push(self.src(argument, scope)?); + } + self.b.apps(head, &all) + } + SemanticTerm::If { + condition, + then_value, + else_value, + } => { + let ty = self.infer(term, scope)?; + let ty = self.lean_ty(&ty)?; + let condition = self.src(condition, scope)?; + let then_value = self.src(then_value, scope)?; + let else_value = self.src(else_value, scope)?; + self.ite(ty, condition, then_value, else_value) + } + SemanticTerm::Match { + scrutinee, + branches, + } => self.src_match(term, scrutinee, branches, scope)?, + SemanticTerm::Add { left, right } => { + let left = self.src(left, scope)?; + let right = self.src(right, scope)?; + self.nat_add(left, right) + } + SemanticTerm::Beq { left, right } => { + let left = self.src(left, scope)?; + let right = self.src(right, scope)?; + let head = self.b.konst("Nat.beq"); + self.b.apps(head, &[left, right]) + } + SemanticTerm::Ble { left, right } => { + let left = self.src(left, scope)?; + let right = self.src(right, scope)?; + let head = self.b.konst("Nat.ble"); + self.b.apps(head, &[left, right]) + } + SemanticTerm::Blt { left, right } => { + let left = self.src(left, scope)?; + let right = self.src(right, scope)?; + self.nat_blt(left, right) + } + SemanticTerm::And { left, right } => { + let left = self.src(left, scope)?; + let right = self.src(right, scope)?; + self.and(left, right) + } + SemanticTerm::Or { left, right } => { + let left = self.src(left, scope)?; + let right = self.src(right, scope)?; + let head = self.b.konst("Bool.or"); + self.b.apps(head, &[left, right]) + } + SemanticTerm::Not { value } => { + let value = self.src(value, scope)?; + self.not(value) + } + SemanticTerm::Let { + binder, + value, + body, + } => { + let (local, generic) = self.gen.let_local(binder, &scope.ctx); + let ty = self.lean_ty(&local.ty.clone())?; + let bound = self.src(value, scope)?; + let free = self.b.free(); + let inner = self.bind(scope, vec![(local, generic)], &[free], scope.env); + let body = self.src(body, &inner)?; + self.b.let_over( + &super::super::identifier(&binder.name), + ty, + bound, + free, + body, + ) + } + SemanticTerm::Pair { left, right } => { + let ty = self.infer(term, scope)?; + let (alpha, beta) = match &ty { + SemanticType::Product { left, right } => (left.as_ref(), right.as_ref()), + _ => return Err("a pair of a non-product type".to_owned()), + }; + let alpha = self.lean_ty(alpha)?; + let beta = self.lean_ty(beta)?; + let left = self.src(left, scope)?; + let right = self.src(right, scope)?; + self.b.pair(alpha, beta, left, right) + } + SemanticTerm::First { value } | SemanticTerm::Second { value } => { + let ty = self.infer(value, scope)?; + let (alpha, beta) = match &ty { + SemanticType::Product { left, right } => (left.as_ref(), right.as_ref()), + _ => return Err("a projection of a non-product".to_owned()), + }; + let alpha = self.lean_ty(alpha)?; + let beta = self.lean_ty(beta)?; + let value = self.src(value, scope)?; + let name = if matches!(term, SemanticTerm::First { value: _ }) { + "Prod.fst" + } else { + "Prod.snd" + }; + let head = self.b.konst00(name); + self.b.apps(head, &[alpha, beta, value]) + } + SemanticTerm::Lambda { + parameters, + captures: _, + body, + } => { + let bound: Vec<(Local, SemanticType)> = parameters + .iter() + .map(|binder| self.gen.let_local(binder, &scope.ctx)) + .collect(); + let mut frees = Vec::new(); + let mut types = Vec::new(); + for (local, _) in &bound { + frees.push(self.b.free()); + types.push(self.lean_ty(&local.ty.clone())?); + } + let inner = self.bind(scope, bound.clone(), &frees, scope.env); + let mut out = self.src(body, &inner)?; + for (position, (local, _)) in bound.iter().enumerate().rev() { + out = self.b.lam_over( + &super::super::identifier(&local.name), + super::EXPLICIT, + types[position], + frees[position], + out, + ); + } + out + } + SemanticTerm::Apply { + function, + arguments, + } => { + let function = self.src(function, scope)?; + let args = self.src_all(arguments, scope)?; + self.b.apps(function, &args) + } + SemanticTerm::FunctionRef { + function, + type_arguments, + } => { + let head = self.member_const(function, &site)?; + let mut all = Vec::new(); + for argument in type_arguments { + let closed = self.gen.source.close(argument, &site); + all.push(self.lean_ty(&closed)?); + } + self.b.apps(head, &all) + } + SemanticTerm::MapLiteral { + key, + value, + entries, + } => { + let key_ty = self.gen.source.close(key, &site); + let value_ty = self.gen.source.close(value, &site); + let alpha = self.lean_ty(&key_ty)?; + let beta = self.lean_ty(&value_ty)?; + let mut items = Vec::new(); + for SemanticMapEntry { key, value } in entries { + let key = self.src(key, scope)?; + let value = self.src(value, scope)?; + items.push(self.b.pair(alpha, beta, key, value)); + } + let entry = self.b.pair_ty(alpha, beta); + self.b.list(entry, &items) + } + SemanticTerm::SetLiteral { element, elements } => { + let element_ty = self.gen.source.close(element, &site); + let ty = self.lean_ty(&element_ty)?; + let items = self.src_all(elements, scope)?; + self.b.list(ty, &items) + } + SemanticTerm::GraphLiteral { node, nodes, edges } => { + let node_ty = self.gen.source.close(node, &site); + let node_ty = self.lean_ty(&node_ty)?; + let targets_ty = self.b.list_ty(node_ty); + let mut items = Vec::new(); + for source in nodes { + let mut targets = Vec::new(); + for SemanticEdge { + source: from, + target, + } in edges + { + if from == source { + targets.push(self.src(target, scope)?); + } + } + let source = self.src(source, scope)?; + let list = self.b.list(node_ty, &targets); + items.push(self.b.pair(node_ty, targets_ty, source, list)); + } + let entry = self.b.pair_ty(node_ty, targets_ty); + self.b.list(entry, &items) + } + SemanticTerm::CheckedApply { + model: _, + type_arguments: _, + arguments: _, + checks: _, + } => { + return Err( + "a checked model application is certified only through its elaboration" + .to_owned(), + ); + } + SemanticTerm::Eq { left: _, right: _ } + | SemanticTerm::Le { left: _, right: _ } + | SemanticTerm::Lt { left: _, right: _ } + | SemanticTerm::PropAnd { left: _, right: _ } + | SemanticTerm::Implies { + premise: _, + conclusion: _, + } + | SemanticTerm::Iff { left: _, right: _ } + | SemanticTerm::Forall { binder: _, body: _ } => { + return Err(format!( + "the formal-only construct `{}` has no runtime denotation", + super::super::super::eligibility::term_key(term) + )); + } + }) + } + + /// A record value: the structure's constructor on its type arguments and + /// its fields in declaration order. + fn record_value( + &mut self, + ty: &SemanticType, + fields: &[SemanticAssignment], + scope: &Scope, + ) -> Result { + let shape = self.gen.source.document(ty)?; + let (member, arguments) = match ty { + SemanticType::Named { member, arguments } => (member, arguments), + _ => return Err("a record of a non-document type".to_owned()), + }; + let owner = self.document_name(member)?; + let mut all = Vec::new(); + for argument in arguments { + all.push(self.lean_ty(argument)?); + } + for field in &shape.field_names { + let SemanticAssignment { field: _, value } = fields + .iter() + .find(|assignment| &assignment.field == field) + .ok_or_else(|| format!("field `{field}` is not assigned"))?; + all.push(self.src(value, scope)?); + } + let head = self.b.konst(&format!("{owner}.mk")); + Ok(self.b.apps(head, &all)) + } + + /// A source `match` as the eliminator of the scrutinee's type. + fn src_match( + &mut self, + term: &SemanticTerm, + scrutinee: &SemanticTerm, + branches: &[SemanticBranch], + scope: &Scope, + ) -> Result { + let scrutinee_ty = self.infer(scrutinee, scope)?; + let result_ty = self.infer(term, scope)?; + let result = self.lean_ty(&result_ty)?; + let discriminant = self.src(scrutinee, scope)?; + let mut alternatives = Vec::new(); + for branch in branches { + let locals = self.gen.branch_locals(branch, scrutinee, &scope.ctx)?; + let mut frees = Vec::new(); + let mut types = Vec::new(); + for (local, _) in &locals { + frees.push(self.b.free()); + types.push(self.lean_ty(&local.ty.clone())?); + } + let inner = self.bind(scope, locals.clone(), &frees, scope.env); + let mut body = self.src(&branch.body, &inner)?; + for (position, (local, _)) in locals.iter().enumerate().rev() { + body = self.b.lam_over( + &super::super::identifier(&local.name), + super::EXPLICIT, + types[position], + frees[position], + body, + ); + } + let (constructor, _) = self.gen.source.branch(branch, &scrutinee_ty, &scope.ctx.site)?; + alternatives.push((self.constructor_position(&constructor)?, body)); + } + alternatives.sort_by_key(|(position, _)| *position); + let minors: Vec = alternatives.into_iter().map(|(_, body)| body).collect(); + self.cases_on_const(&scrutinee_ty, discriminant, result, &minors) + } + + /// A constructor's position among its type's, in declaration order. + pub(super) fn constructor_position(&self, constructor: &Constructor) -> Result { + Ok(match constructor { + Constructor::Bool(false) | Constructor::Zero | Constructor::Nil => 0, + Constructor::Bool(true) | Constructor::Succ | Constructor::Cons => 1, + Constructor::OptionNone => 0, + Constructor::OptionSome => 1, + Constructor::Error => 0, + Constructor::Ok => 1, + Constructor::Violation(_, _) => 0, + Constructor::Document { ty: _, index } => *index, + }) + } +} diff --git a/crates/lexlean/src/production/certificate/data_a/templates.rs b/crates/lexlean/src/production/certificate/data_a/templates.rs new file mode 100644 index 000000000..e70fcce1f --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/templates.rs @@ -0,0 +1,33 @@ +//! Collection templates and key comparisons (SPEC.md §17.17.3). + +use super::{Pf, Scope, Writer}; +use crate::calculus::Expr; +use crate::ir::semantic::{SemanticTerm, SemanticType}; + +impl<'a> Writer<'_, 'a> { + /// A template call. + pub(super) fn template_call( + &mut self, + template: crate::calculus::library::Template, + instance: &[SemanticType], + arguments: &[SemanticTerm], + types: &[SemanticType], + expr: &'a Expr, + scope: &Scope, + ) -> Result { + let _ = (template, instance, arguments, types, expr, scope); + Err("collection templates".to_owned()) + } + + /// A key comparison. + pub(super) fn less_than( + &mut self, + types: &[SemanticType], + arguments: &[SemanticTerm], + expr: &'a Expr, + scope: &Scope, + ) -> Result { + let _ = (types, arguments, expr, scope); + Err("key comparisons".to_owned()) + } +} diff --git a/crates/lexlean/src/production/kernel/ext.rs b/crates/lexlean/src/production/kernel/ext.rs index 724dd1e04..e3d2ea89e 100644 --- a/crates/lexlean/src/production/kernel/ext.rs +++ b/crates/lexlean/src/production/kernel/ext.rs @@ -40,6 +40,100 @@ pub enum Given { } impl Builder { + /// The nodes reachable from `roots`, child before parent, with the + /// position each root has among them: the DAG a module keeps, without the + /// intermediate terms the construction passed through. + #[must_use] + pub fn compact(&self, roots: &[Term]) -> (Vec, Vec) { + let mut position: HashMap = HashMap::new(); + let mut out: Vec = Vec::new(); + for &root in roots { + let mut stack: Vec<(Term, bool)> = vec![(root, false)]; + while let Some((term, children_done)) = stack.pop() { + if position.contains_key(&term) { + continue; + } + let node = &self.nodes[term]; + if !children_done { + stack.push((term, true)); + match node { + CoreNode::BVar { .. } + | CoreNode::Sort { .. } + | CoreNode::Const { .. } + | CoreNode::Nat { .. } + | CoreNode::String { .. } => {} + CoreNode::App { f, x } => { + stack.push((*x, false)); + stack.push((*f, false)); + } + CoreNode::Lambda { t, v, .. } | CoreNode::Forall { t, v, .. } => { + stack.push((*v, false)); + stack.push((*t, false)); + } + CoreNode::Let { t, v, body, .. } => { + stack.push((*body, false)); + stack.push((*v, false)); + stack.push((*t, false)); + } + CoreNode::Mdata { x, .. } | CoreNode::Projection { x, .. } => { + stack.push((*x, false)); + } + } + continue; + } + let at = |term: &Term| position[term]; + let moved = match node.clone() { + CoreNode::App { f, x } => CoreNode::App { + f: at(&f), + x: at(&x), + }, + CoreNode::Lambda { n, b, t, v } => CoreNode::Lambda { + n, + b, + t: at(&t), + v: at(&v), + }, + CoreNode::Forall { n, b, t, v } => CoreNode::Forall { + n, + b, + t: at(&t), + v: at(&v), + }, + CoreNode::Let { n, t, v, body, d } => CoreNode::Let { + n, + t: at(&t), + v: at(&v), + body: at(&body), + d, + }, + CoreNode::Mdata { m, x } => CoreNode::Mdata { m, x: at(&x) }, + CoreNode::Projection { n, s, i, x } => CoreNode::Projection { + n, + s, + i, + x: at(&x), + }, + atom @ (CoreNode::BVar { .. } + | CoreNode::Sort { .. } + | CoreNode::Const { .. } + | CoreNode::Nat { .. } + | CoreNode::String { .. }) => atom, + }; + position.insert(term, out.len()); + out.push(moved); + } + } + let mapped = roots.iter().map(|root| position[root]).collect(); + (out, mapped) + } + + /// A natural-number literal of any size, from its digits. + pub fn nat_text(&mut self, digits: &str) -> Term { + self.push(CoreNode::Nat { + v: digits.to_owned(), + }) + } + /// A fresh free variable. pub fn free(&mut self) -> Term { let id = self.fresh; @@ -176,6 +270,69 @@ impl Builder { }) } + /// `term` with each node of `from` replaced by the node of `to` at its + /// position, wherever it occurs (a free variable, in practice). + pub fn substitute(&mut self, term: Term, from: &[Term], to: &[Term]) -> Term { + let map: HashMap = from.iter().copied().zip(to.iter().copied()).collect(); + if map.is_empty() { + return term; + } + let mut memo = HashMap::new(); + self.substitute_memo(term, &map, &mut memo) + } + + fn substitute_memo( + &mut self, + term: Term, + map: &HashMap, + memo: &mut HashMap, + ) -> Term { + if let Some(&replacement) = map.get(&term) { + return replacement; + } + if let Some(&done) = memo.get(&term) { + return done; + } + let out = match self.nodes[term].clone() { + CoreNode::BVar { .. } + | CoreNode::Sort { .. } + | CoreNode::Const { .. } + | CoreNode::Nat { .. } + | CoreNode::String { .. } => term, + CoreNode::App { f, x } => { + let f = self.substitute_memo(f, map, memo); + let x = self.substitute_memo(x, map, memo); + self.app(f, x) + } + CoreNode::Lambda { n, b, t, v } => { + let t = self.substitute_memo(t, map, memo); + let v = self.substitute_memo(v, map, memo); + self.lam(&n, b, t, v) + } + CoreNode::Forall { n, b, t, v } => { + let t = self.substitute_memo(t, map, memo); + let v = self.substitute_memo(v, map, memo); + self.pi(&n, b, t, v) + } + CoreNode::Let { n, t, v, body, d } => { + let t = self.substitute_memo(t, map, memo); + let v = self.substitute_memo(v, map, memo); + let body = self.substitute_memo(body, map, memo); + self.let_node(&n, t, v, body, d) + } + CoreNode::Mdata { m, x } => { + let x = self.substitute_memo(x, map, memo); + self.mdata_node(m, x) + } + CoreNode::Projection { n, s, i, x } => { + let x = self.substitute_memo(x, map, memo); + self.projection(&n, &s, i, x) + } + }; + memo.insert(term, out); + out + } + /// `term` with every constant named in `map` replaced by its term. pub fn replace_consts(&mut self, term: Term, map: &HashMap) -> Term { if map.is_empty() { @@ -305,32 +462,46 @@ impl Builder { #[must_use] pub fn has_placeholder(&self, term: Term) -> bool { let mut seen = std::collections::HashSet::new(); - self.placeholder_walk(term, &mut seen) + self.placeholder_walk(term, &mut seen, true) + } + + /// Whether `term` mentions a lemma's placeholder, a binder nothing solved. + #[must_use] + pub fn has_unsolved(&self, term: Term) -> bool { + let mut seen = std::collections::HashSet::new(); + self.placeholder_walk(term, &mut seen, false) } - fn placeholder_walk(&self, term: Term, seen: &mut std::collections::HashSet) -> bool { + fn placeholder_walk( + &self, + term: Term, + seen: &mut std::collections::HashSet, + free_too: bool, + ) -> bool { if !seen.insert(term) { return false; } match &self.nodes[term] { - CoreNode::Const { n, .. } => n.starts_with(FREE) || n.starts_with(META), + CoreNode::Const { n, .. } => { + n.starts_with(META) || (free_too && n.starts_with(FREE)) + } CoreNode::BVar { .. } | CoreNode::Sort { .. } | CoreNode::Nat { .. } | CoreNode::String { .. } => false, CoreNode::App { f, x } => { - self.placeholder_walk(*f, seen) || self.placeholder_walk(*x, seen) + self.placeholder_walk(*f, seen, free_too) || self.placeholder_walk(*x, seen, free_too) } CoreNode::Lambda { t, v, .. } | CoreNode::Forall { t, v, .. } => { - self.placeholder_walk(*t, seen) || self.placeholder_walk(*v, seen) + self.placeholder_walk(*t, seen, free_too) || self.placeholder_walk(*v, seen, free_too) } CoreNode::Let { t, v, body, .. } => { - self.placeholder_walk(*t, seen) - || self.placeholder_walk(*v, seen) - || self.placeholder_walk(*body, seen) + self.placeholder_walk(*t, seen, free_too) + || self.placeholder_walk(*v, seen, free_too) + || self.placeholder_walk(*body, seen, free_too) } CoreNode::Mdata { x, .. } | CoreNode::Projection { x, .. } => { - self.placeholder_walk(*x, seen) + self.placeholder_walk(*x, seen, free_too) } } } @@ -621,6 +792,46 @@ pub struct Open { next: usize, } +impl Builder { + /// `Eq.refl` at the equation `domain` states; for a statement under + /// binders, the function from them. A placeholder on a side of the + /// equation is solved by the other side. + fn refl_proof( + &mut self, + name: &str, + position: usize, + domain: Term, + solved: &mut HashMap, + ) -> Result { + if let CoreNode::Forall { n, b: info, t, v } = self.nodes[domain].clone() { + let free = self.free(); + let body = self.instantiate_beta(v, free); + let inner = self.refl_proof(name, position, body, solved)?; + return Ok(self.lam_over(&n, info, t, free, inner)); + } + let (eq, parts) = self.spine(domain); + let levels = match &self.nodes[eq] { + CoreNode::Const { n, u } if n == "Eq" && parts.len() == 3 => u.clone(), + _ => { + return Err(format!( + "`{name}`: argument {position} is a reflexivity proof at {}, which is not an equation", + self.show(domain) + )); + } + }; + if let Some(solution) = self.meta_id(parts[2]) { + solved.entry(solution).or_insert(parts[1]); + } + if let Some(solution) = self.meta_id(parts[1]) { + solved.entry(solution).or_insert(parts[2]); + } + let refl = self.konst_at("Eq.refl", levels); + let sides = self.resolve(parts[1], solved); + let carrier = self.resolve(parts[0], solved); + Ok(self.apps(refl, &[carrier, sides])) + } +} + impl Open { fn explicit_at(&self, position: usize) -> Option { self.binders @@ -692,27 +903,7 @@ impl Open { } Given::Refl => { let domain = b.resolve(domain, &self.solved); - let (eq, parts) = b.spine(domain); - let levels = match &b.nodes[eq] { - CoreNode::Const { n, u } if n == "Eq" && parts.len() == 3 => u.clone(), - _ => { - return Err(format!( - "`{}`: argument {position} is a reflexivity proof at {}, which is not an equation", - self.name, - b.show(domain) - )); - } - }; - if let Some(solution) = b.meta_id(parts[2]) { - self.solved.entry(solution).or_insert(parts[1]); - } - if let Some(solution) = b.meta_id(parts[1]) { - self.solved.entry(solution).or_insert(parts[2]); - } - let refl = b.konst_at("Eq.refl", levels); - let sides = b.resolve(parts[1], &self.solved); - let carrier = b.resolve(parts[0], &self.solved); - b.apps(refl, &[carrier, sides]) + b.refl_proof(&self.name, position, domain, &mut self.solved)? } }; self.solved.insert(id, value); @@ -735,7 +926,7 @@ impl Open { } let term = b.resolve(term, &self.solved); let ty = b.resolve(self.conclusion, &self.solved); - if b.has_placeholder(term) || b.has_placeholder(ty) { + if b.has_unsolved(term) || b.has_unsolved(ty) { let open: Vec<&str> = self .binders .iter() From 0580d2043904e396af362160abc00dbc4ea41656 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 16:32:03 +0000 Subject: [PATCH 04/41] #89 stage 3: certificate A data: lets by substitution, axioms by constants, runtime pins --- crates/lexlean/src/production/certificate.rs | 1 + .../src/production/certificate/data_a.rs | 10 +++- .../src/production/certificate/data_a/lean.rs | 2 +- .../production/certificate/data_a/prims.rs | 1 + .../production/certificate/data_a/prove.rs | 58 +++++++++++++------ crates/lexlean/src/production/kernel/ext.rs | 22 +++++-- 6 files changed, 71 insertions(+), 23 deletions(-) diff --git a/crates/lexlean/src/production/certificate.rs b/crates/lexlean/src/production/certificate.rs index 1aaa29c8e..22a487101 100644 --- a/crates/lexlean/src/production/certificate.rs +++ b/crates/lexlean/src/production/certificate.rs @@ -72,6 +72,7 @@ pub const LIBRARY_MODULES: [&str; 7] = [ const SYNTAX: &str = "LexLeanTarget.TargetSyntax"; const SEMANTICS: &str = "LexLeanTarget.TargetSemantics"; +const SEMANTICS_MODULE: &str = SEMANTICS; const NAT_BOUND: &str = "18446744073709551616"; /// One generated certificate module. diff --git a/crates/lexlean/src/production/certificate/data_a.rs b/crates/lexlean/src/production/certificate/data_a.rs index d05c0b228..5cf0f5326 100644 --- a/crates/lexlean/src/production/certificate/data_a.rs +++ b/crates/lexlean/src/production/certificate/data_a.rs @@ -263,7 +263,7 @@ impl<'g, 'a> Writer<'g, 'a> { } } if let Some((module, rest)) = name.split_once(".LexLeanRuntime.") { - if self.runtime_modules.contains(module) { + if module == super::SEMANTICS_MODULE || self.runtime_modules.contains(module) { return Ok(runtime_axioms(rest)); } } @@ -332,6 +332,14 @@ impl<'g, 'a> Writer<'g, 'a> { "the row `{full}` of certificate A has a loose bound variable" )); } + if std::env::var("LEXLEAN_SHOW").is_ok_and(|wanted| full.ends_with(&wanted)) { + let text = format!("{full} : {}\n:= {}\n", self.b.show(ty), self.b.show(value)); + if let Ok(path) = std::env::var("LEXLEAN_SHOW_FILE") { + let _ = std::fs::write(path, text); + } else { + eprintln!("{text}"); + } + } let axioms = self.row_axiom_set(&[ty, value])?; self.row_axioms.insert(full.clone(), axioms.clone()); self.rows.push(Row { diff --git a/crates/lexlean/src/production/certificate/data_a/lean.rs b/crates/lexlean/src/production/certificate/data_a/lean.rs index 5b210714a..e59278ec3 100644 --- a/crates/lexlean/src/production/certificate/data_a/lean.rs +++ b/crates/lexlean/src/production/certificate/data_a/lean.rs @@ -443,7 +443,7 @@ impl Writer<'_, '_> { } else { self.b.konst(&format!("{type_name}.instNeg")) }; - self.b.apps(neg, &[ty, ty, instance, positive]) + self.b.apps(neg, &[ty, instance, positive]) } pub(super) fn bytes_term(&mut self, hex: &str) -> Result { diff --git a/crates/lexlean/src/production/certificate/data_a/prims.rs b/crates/lexlean/src/production/certificate/data_a/prims.rs index 6ea4397d4..accc0550e 100644 --- a/crates/lexlean/src/production/certificate/data_a/prims.rs +++ b/crates/lexlean/src/production/certificate/data_a/prims.rs @@ -170,6 +170,7 @@ impl<'a> Writer<'_, 'a> { })?; let alpha = self.lean_ty(&element)?; let index = self.runtime_const(site, "listIndex")?; + let index = self.b.app(index, alpha); PrimRule { lemma: "prim_index_list".to_owned(), hints: vec![("α", alpha)], diff --git a/crates/lexlean/src/production/certificate/data_a/prove.rs b/crates/lexlean/src/production/certificate/data_a/prove.rs index 45c9c83d2..aed7a0865 100644 --- a/crates/lexlean/src/production/certificate/data_a/prove.rs +++ b/crates/lexlean/src/production/certificate/data_a/prove.rs @@ -48,8 +48,15 @@ impl<'a> Writer<'_, 'a> { self.b.apps(conv, &[p, env, e, rel]) } - /// The width predicate and value a derivation's type relates. - pub(super) fn rel_parts(&self, ty: Term) -> Result<(Term, Term), String> { + /// The width predicate and value a derivation's type relates. A type + /// under `let` bindings relates the bindings applied to each. + pub(super) fn rel_parts(&mut self, ty: Term) -> Result<(Term, Term), String> { + if let crate::ir::core::CoreNode::Let { n, t, v, body, d } = self.b.node(ty).clone() { + let (fits, value) = self.rel_parts(body)?; + let fits = self.b.let_node(&n, t, v, fits, d); + let value = self.b.let_node(&n, t, v, value, d); + return Ok((fits, value)); + } let (_, args) = self.b.spine(ty); let observation = args.get(3).copied().ok_or("a derivation type with no relation")?; let (_, parts) = self.b.spine(observation); @@ -63,12 +70,12 @@ impl<'a> Writer<'_, 'a> { } /// The width predicate of a derivation. - pub(super) fn fits_of(&self, pf: Pf) -> Result { + pub(super) fn fits_of(&mut self, pf: Pf) -> Result { Ok(self.rel_parts(pf.ty)?.0) } /// The value of a derivation. - pub(super) fn value_of(&self, pf: Pf) -> Result { + pub(super) fn value_of(&mut self, pf: Pf) -> Result { Ok(self.rel_parts(pf.ty)?.1) } @@ -528,7 +535,22 @@ impl<'a> Writer<'_, 'a> { Expr::First { value } | Expr::Second { value } => value.as_ref(), _ => return Err("the lowering of a projection is not first or second".to_owned()), }; + let ty = self.infer(value, scope)?; + let (left, right) = match &ty { + SemanticType::Product { left, right } => (left.as_ref(), right.as_ref()), + _ => return Err("a projection of a non-product".to_owned()), + }; let inner = self.prove(value, inner_expr, scope)?; + // The pair's encoding unfolds to the pair of its components'. + let src = self.src(value, scope)?; + let (alpha, beta) = (self.lean_ty(left)?, self.lean_ty(right)?); + let fst = self.b.konst00("Prod.fst"); + let fst = self.b.apps(fst, &[alpha, beta, src]); + let snd = self.b.konst00("Prod.snd"); + let snd = self.b.apps(snd, &[alpha, beta, src]); + let (enc_left, enc_right) = (self.enc(left)?, self.enc(right)?); + let a = self.b.app(enc_left, fst); + let b = self.b.app(enc_right, snd); let name = if matches!(term, SemanticTerm::First { value: _ }) { "conv_first" } else { @@ -536,7 +558,7 @@ impl<'a> Writer<'_, 'a> { }; self.apply( name, - &[("p", p), ("env", env)], + &[("p", p), ("env", env), ("a", a), ("b", b)], &[Given::Proof(inner.proof, inner.ty)], ) } @@ -706,7 +728,8 @@ impl<'a> Writer<'_, 'a> { let mut out = self.built(scope, cursor, &[], nil)?; for (item, node) in items.iter().zip(spine).rev() { let h = self.value_of(*item)?; - let t = self.list_payload(self.value_of(out)?)?; + let tail_value = self.value_of(out)?; + let t = self.list_payload(tail_value)?; let construct = self.apply("construct_cons", &[("h", h), ("t", t)], &[])?; out = self.built(scope, node, &[*item, out], construct)?; } @@ -878,8 +901,11 @@ impl<'a> Writer<'_, 'a> { let else_branch = self.guarded(condition_src, fls, else_pf)?; self.apply( "conv_cond", - &[("p", p), ("env", env), ("P", predicate), ("V", value_fn), ("b", condition_src)], + &[("p", p), ("env", env)], &[ + Given::Term(predicate), + Given::Term(value_fn), + Given::Term(condition_src), Given::Proof(tested.proof, tested.ty), Given::Proof(then_branch.0, then_branch.1), Given::Proof(else_branch.0, else_branch.1), @@ -926,27 +952,25 @@ impl<'a> Writer<'_, 'a> { let enc = self.enc(&local_ty)?; let encoded = self.b.app(enc, free); let name = self.nat_u(number); - let value_ty = self.value_ty(); - let nat = self.b.konst("Nat"); - let entry = self.b.pair(nat, value_ty, name, encoded); - let entry_ty = self.b.pair_ty(nat, value_ty); - let cons = self.b.konst0("List.cons"); - let env = self.b.apps(cons, &[entry_ty, entry, scope.env]); + let env = self.extend_env(scope.env, &[number], &[encoded])?; let inner = self.bind(scope, vec![(local, generic)], &[free], env); let rest = self.prove(body, body_expr, &inner)?; let (p, outer) = (self.prog, scope.env); let target = self.ty_term(ty); let applied = self.apply( "conv_let", - &[("p", p), ("env", outer), ("t", target)], + &[("p", p), ("env", outer), ("t", target), ("x", name)], &[ Given::Proof(bound.proof, bound.ty), Given::Proof(rest.proof, rest.ty), ], )?; - let label = super::super::identifier(&binder.name); - let proof = self.b.let_over(&label, lean, bound_src, free, applied.proof); - let ty = self.b.let_over(&label, lean, bound_src, free, applied.ty); + // The binding is substituted, not bound: the type of the derivation + // stays the `Conv` of the lowered `let` for the derivation above it to + // read, and the kernel sees the same term either way. + let _ = lean; + let proof = self.b.substitute(applied.proof, &[free], &[bound_src]); + let ty = self.b.substitute(applied.ty, &[free], &[bound_src]); Ok(Pf { proof, ty }) } diff --git a/crates/lexlean/src/production/kernel/ext.rs b/crates/lexlean/src/production/kernel/ext.rs index e3d2ea89e..86536e190 100644 --- a/crates/lexlean/src/production/kernel/ext.rs +++ b/crates/lexlean/src/production/kernel/ext.rs @@ -398,7 +398,7 @@ impl Builder { } fn show_at(&self, term: Term, names: &mut Vec, depth: usize) -> String { - if depth > 40 { + if depth > 4000 { return "…".to_owned(); } match &self.nodes[term] { @@ -520,9 +520,23 @@ impl Builder { return; } match (&self.nodes[pattern], &self.nodes[actual]) { - (CoreNode::App { f, x }, CoreNode::App { f: g, x: y }) => { - self.solve(*f, *g, solved); - self.solve(*x, *y, solved); + (CoreNode::App { .. }, CoreNode::App { .. }) => { + // Two applications are compared argument by argument only when + // they apply the same head to as many arguments: otherwise one + // is a definition applied that the other unfolds, which the + // kernel compares. + let (head, args) = self.spine(pattern); + let (other_head, other_args) = self.spine(actual); + let same_head = match (&self.nodes[head], &self.nodes[other_head]) { + (CoreNode::Const { n, .. }, CoreNode::Const { n: m, .. }) => n == m, + (CoreNode::BVar { i }, CoreNode::BVar { i: j }) => i == j, + _ => head == other_head, + }; + if same_head && args.len() == other_args.len() { + for (a, b) in args.iter().zip(&other_args) { + self.solve(*a, *b, solved); + } + } } ( CoreNode::Lambda { t, v, .. } | CoreNode::Forall { t, v, .. }, From 3b01fcda264d7248e97ee4d171de2c600a3e708e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 16:38:30 +0000 Subject: [PATCH 05/41] #89 stage 3: certificate A data: encoders and recursion plans --- .../src/production/certificate/data.rs | 20 +- .../src/production/certificate/data_a.rs | 41 +- .../production/certificate/data_a/encoders.rs | 436 +++++++++++++++++ .../production/certificate/data_a/recplan.rs | 439 ++++++++++++++++++ .../src/production/certificate/data_a/rows.rs | 8 - .../src/production/certificate/data_a/src.rs | 37 +- 6 files changed, 953 insertions(+), 28 deletions(-) create mode 100644 crates/lexlean/src/production/certificate/data_a/encoders.rs create mode 100644 crates/lexlean/src/production/certificate/data_a/recplan.rs diff --git a/crates/lexlean/src/production/certificate/data.rs b/crates/lexlean/src/production/certificate/data.rs index a66b7cdee..34c3c06be 100644 --- a/crates/lexlean/src/production/certificate/data.rs +++ b/crates/lexlean/src/production/certificate/data.rs @@ -1338,20 +1338,20 @@ impl<'g, 'a> Composer<'g, 'a> { /// A container the kernel nests in a recursive group of document types: an /// auxiliary inductive type of the group's recursors, with its own motive. -struct Aux { +pub(super) struct Aux { /// The container as the source writes it, which certificate A's nested /// auxiliary encoder is registered under. - original: SemanticType, + pub(super) original: SemanticType, /// The container as Lean sees it: a set as a list, a map as a list of /// pairs. - canonical: SemanticType, - kind: AuxKind, + pub(super) canonical: SemanticType, + pub(super) kind: AuxKind, /// Certificate A's auxiliary for the container (`__items_n`, `__aux_n`). - nested_index: u64, + pub(super) nested_index: u64, } #[derive(Clone, Copy, PartialEq, Eq)] -enum AuxKind { +pub(super) enum AuxKind { List, Option, Prod, @@ -1377,7 +1377,7 @@ struct Group { /// A type as Lean's kernel sees it: a set is a list, a map a list of pairs, /// a contract violation or reasoning failure a pair of Booleans. -fn canonical(ty: &SemanticType) -> SemanticType { +pub(super) fn canonical(ty: &SemanticType) -> SemanticType { match ty { SemanticType::Set { element } | SemanticType::List { element } => SemanticType::List { element: Box::new(canonical(element)), @@ -2066,7 +2066,7 @@ impl Composer<'_, '_> { /// itself: the element of a list, set, or map (its entries), the value of an /// option, the two components of a pair, and the error then the value of a /// result. -fn container_parts(ty: &SemanticType) -> Result, String> { +pub(super) fn container_parts(ty: &SemanticType) -> Result, String> { Ok(match ty { SemanticType::List { element } | SemanticType::Set { element } => { vec![element.as_ref().clone()] @@ -2113,7 +2113,7 @@ fn container_parts(ty: &SemanticType) -> Result, String> { } /// The type arguments a document type is instantiated at. -fn type_arguments(ty: &SemanticType) -> Vec { +pub(super) fn type_arguments(ty: &SemanticType) -> Vec { match ty { SemanticType::Named { member: _, @@ -2153,7 +2153,7 @@ fn type_arguments(ty: &SemanticType) -> Vec { } /// The member a document type names. -fn named_member(ty: &SemanticType) -> Result { +pub(super) fn named_member(ty: &SemanticType) -> Result { match ty { SemanticType::Named { member, diff --git a/crates/lexlean/src/production/certificate/data_a.rs b/crates/lexlean/src/production/certificate/data_a.rs index 5cf0f5326..7282e670c 100644 --- a/crates/lexlean/src/production/certificate/data_a.rs +++ b/crates/lexlean/src/production/certificate/data_a.rs @@ -28,16 +28,18 @@ use super::{components, CLocal, Ctx, Gen, Subject, SYNTAX}; use crate::calculus::Expr; use crate::ir::core::{ CoreAxiomPolicy, CoreBinderInfo, CoreDeclKind, CoreDeclaration, CoreModule, CoreReducibility, - CoreTransparency, CORE_SPEC_2, + CoreSafety, CoreTransparency, CORE_SPEC_2, }; use crate::ir::semantic::SemanticType; mod boundary; mod calls; mod lean; +mod encoders; mod matching; mod prims; mod prove; +mod recplan; mod rows; mod src; mod templates; @@ -52,6 +54,11 @@ pub(super) enum RowKind { /// A definition; `compiled` is whether the code generator runs over it, /// which the differential evaluator needs of `denote` and what it calls. Definition { compiled: bool }, + /// The compiler's implementation record of a recursive definition: the + /// same function written with direct recursion, for the code generator, + /// which has no code for a recursor. The members of one mutual group + /// name each other. + Partial, } /// One row of the module. @@ -62,6 +69,8 @@ pub(super) struct Row { pub(super) value: Term, pub(super) kind: RowKind, pub(super) axioms: BTreeSet, + /// The rows of the mutual group the row belongs to, itself included. + pub(super) group: Option>, } /// A proof of a program expression's relation, with its type @@ -348,10 +357,25 @@ impl<'g, 'a> Writer<'g, 'a> { value, kind, axioms, + group: None, }); Ok(()) } + /// Name the rows of one mutual group (their short names) as each other's + /// group. + pub(super) fn group_rows(&mut self, short_names: &[String]) { + let full: Vec = short_names + .iter() + .map(|name| format!("{}.{name}", self.name)) + .collect(); + for row in &mut self.rows { + if full.contains(&row.name) { + row.group = Some(full.clone()); + } + } + } + /// The module. pub(super) fn finish(self, imports: Vec) -> Result { let heights = self.heights(); @@ -432,16 +456,23 @@ impl<'g, 'a> Writer<'g, 'a> { } fn declaration(row: &Row, height: u32, ty: Term, value: Term) -> CoreDeclaration { - let (kind, reducibility, compiled) = match row.kind { - RowKind::Theorem => (CoreDeclKind::Theorem, None, false), + let (kind, reducibility, compiled, safety) = match row.kind { + RowKind::Theorem => (CoreDeclKind::Theorem, None, false, None), RowKind::Definition { compiled } => ( CoreDeclKind::Definition, Some(CoreReducibility::Regular(height + 1)), compiled, + None, + ), + RowKind::Partial => ( + CoreDeclKind::Definition, + Some(CoreReducibility::Opaque), + false, + Some(CoreSafety::Partial), ), }; CoreDeclaration { - all: Some(vec![row.name.clone()]), + all: Some(row.group.clone().unwrap_or_else(|| vec![row.name.clone()])), name: row.name.clone(), levels: Vec::new(), kind, @@ -454,7 +485,7 @@ fn declaration(row: &Row, height: u32, ty: Term, value: Term) -> CoreDeclaration class: false, instance: None, generated: false, - safety: None, + safety, compiled, tags: Vec::new(), no_confusion: None, diff --git a/crates/lexlean/src/production/certificate/data_a/encoders.rs b/crates/lexlean/src/production/certificate/data_a/encoders.rs new file mode 100644 index 000000000..179d9bcf0 --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/encoders.rs @@ -0,0 +1,436 @@ +//! The encoders of the document types a program realizes (SPEC.md §17.17.3): +//! `__enc_k` for each type, and for a recursive group of types the auxiliary +//! encoders of the containers nested in them with their bundles. + +use super::recplan::{PositionKind, RecPlan}; +use super::super::data::AuxKind; +use super::super::components; +use super::{RowKind, Writer, EXPLICIT}; +use crate::ir::core::CoreLevel; +use crate::ir::semantic::SemanticType; +use crate::production::kernel::{Given, Term}; + +/// The short name of the row that encodes a position. +fn row_name(plan: &RecPlan, position: usize) -> Result { + match &plan.positions[position].kind { + PositionKind::Block { index } => Ok(format!("__enc_{index}")), + PositionKind::Aux(aux) => Ok(match aux.kind { + AuxKind::List => format!("__items_{}", aux.nested_index), + AuxKind::Option | AuxKind::Prod | AuxKind::Except => { + format!("__aux_{}", aux.nested_index) + } + }), + PositionKind::Builtin => Err("a builtin position has no encoder row".to_owned()), + } +} + +impl<'a> Writer<'_, 'a> { + /// The encoder rows of every document type of the layout, dependencies + /// first. + pub(super) fn encoders(&mut self) -> Result<(), String> { + let graph = self.gen.adt_graph()?; + for component in components(&graph) { + let recursive = component.len() > 1 + || component.iter().any(|index| { + self.gen + .nested + .values() + .any(|nested| nested.group == *index) + || graph.get(index).is_some_and(|edges| edges.contains(index)) + }); + if recursive { + self.group_encoders(&component)?; + } else { + for index in component { + self.plain_encoder(index)?; + } + } + } + Ok(()) + } + + /// The encoder of a document type outside any recursive group. + fn plain_encoder(&mut self, index: u64) -> Result<(), String> { + let ty = self.gen.layout.adts[index as usize].ty.clone(); + let shape = self.gen.source.document(&ty)?; + let lean = self.lean_ty(&ty)?; + let value_ty = self.value_ty(); + let adt = self.ts("Value.adt"); + let value = if shape.kind == "inductive" { + let mut minors = Vec::new(); + for (position, fields) in shape.fields.iter().enumerate() { + let mut frees = Vec::new(); + let mut types = Vec::new(); + let mut encoded = Vec::new(); + for field in fields { + let free = self.b.free(); + frees.push(free); + types.push(self.lean_ty(field)?); + let enc = self.enc(field)?; + encoded.push(self.b.app(enc, free)); + } + let list = self.b.list(value_ty, &encoded); + let tag = self.nat_u(position as u64); + let mut minor = self.b.apps(adt, &[tag, list]); + for (position, free) in frees.iter().enumerate().rev() { + minor = self.b.lam_over( + &format!("__x{position}"), + EXPLICIT, + types[position], + *free, + minor, + ); + } + minors.push(minor); + } + let free = self.b.free(); + let cases = self.cases_on_const(&ty, free, value_ty, &minors)?; + self.b.lam_over("__v", EXPLICIT, lean, free, cases) + } else { + let free = self.b.free(); + let mut encoded = Vec::new(); + for (name, field) in shape.field_names.iter().zip(&shape.fields[0]) { + let projected = self.project_term(&ty, name, free)?; + let enc = self.enc(field)?; + encoded.push(self.b.app(enc, projected)); + } + let list = self.b.list(value_ty, &encoded); + let tag = self.nat_u(0); + let body = self.b.apps(adt, &[tag, list]); + self.b.lam_over("__s", EXPLICIT, lean, free, body) + }; + let row_ty = self.b.arrow(lean, value_ty); + self.push_row( + &format!("__enc_{index}"), + RowKind::Definition { compiled: true }, + row_ty, + value, + ) + } + + /// The value one field of a constructor contributes to its encoding: + /// the recursive value standing for a recursive field, else the field's + /// own encoding. + fn field_encoding( + &mut self, + plan: &RecPlan, + field_ty: &SemanticType, + field: Term, + recursive: Option<(usize, Term)>, + ) -> Result { + match recursive { + Some((position, value)) => { + if matches!( + &plan.positions[position].kind, + PositionKind::Aux(aux) if aux.kind == AuxKind::List + ) { + let wrap = self.ts("Value.list"); + Ok(self.b.app(wrap, value)) + } else { + Ok(value) + } + } + None => { + let enc = self.enc(field_ty)?; + Ok(self.b.app(enc, field)) + } + } + } + + /// The encoding of the constructor `ctor` of `position`, over its fields + /// and the recursive values of its recursive fields. + fn constructor_encoding( + &mut self, + plan: &RecPlan, + position: usize, + ctor: usize, + fields: &[Term], + recursive: &[Option], + ) -> Result { + let entry = &plan.positions[position]; + let shape = &entry.constructors[ctor]; + let mut parts = Vec::new(); + for (field_index, field_ty) in shape.fields.iter().enumerate() { + let rec = shape.recursive[field_index].zip(recursive[field_index]); + // The tail of a list auxiliary is the auxiliary itself, whose + // value is already a list of encodings. + let is_list_tail = matches!(&entry.kind, + PositionKind::Aux(aux) if aux.kind == AuxKind::List && field_index == 1); + if is_list_tail { + parts.push(recursive[1].ok_or("a list auxiliary whose tail is not recursive")?); + } else { + parts.push(self.field_encoding(plan, field_ty, fields[field_index], rec)?); + } + } + let value_ty = self.value_ty(); + match &entry.kind { + PositionKind::Block { index: _ } => { + let list = self.b.list(value_ty, &parts); + let tag = self.nat_u(ctor as u64); + let adt = self.ts("Value.adt"); + Ok(self.b.apps(adt, &[tag, list])) + } + PositionKind::Aux(aux) => match aux.kind { + AuxKind::List => { + if ctor == 0 { + let nil = self.b.konst0("List.nil"); + Ok(self.b.app(nil, value_ty)) + } else { + let cons = self.b.konst0("List.cons"); + Ok(self.b.apps(cons, &[value_ty, parts[0], parts[1]])) + } + } + AuxKind::Option => { + if ctor == 0 { + Ok(self.ts("Value.none")) + } else { + let some = self.ts("Value.some"); + Ok(self.b.app(some, parts[0])) + } + } + AuxKind::Prod => { + let pair = self.ts("Value.pair"); + Ok(self.b.apps(pair, &[parts[0], parts[1]])) + } + AuxKind::Except => { + let head = self.ts(if ctor == 0 { "Value.error" } else { "Value.ok" }); + Ok(self.b.app(head, parts[0])) + } + }, + PositionKind::Builtin => Err("a builtin position has no encoding".to_owned()), + } + } + + /// The result type of the motive of a position: a list of encodings for a + /// list auxiliary, else an encoding. + fn result_type(&mut self, plan: &RecPlan, position: usize) -> Term { + let value_ty = self.value_ty(); + match &plan.positions[position].kind { + PositionKind::Aux(aux) if aux.kind == AuxKind::List => self.b.list_ty(value_ty), + PositionKind::Aux(_) | PositionKind::Block { .. } | PositionKind::Builtin => value_ty, + } + } + + /// The rows of a recursive group of document types and the containers + /// nested in them: each position's encoder by the group's recursor, its + /// implementation record for the code generator, and the bundles of the + /// list and option containers. + #[allow(clippy::too_many_lines)] + fn group_encoders(&mut self, component: &[u64]) -> Result<(), String> { + let plan = self.group_plan(component)?; + let one = CoreLevel::Succ(Box::new(CoreLevel::Zero)); + let mut params = Vec::new(); + for argument in &plan.params { + params.push(self.lean_ty(argument)?); + } + // The motives: each position into the type its encoding has. + let mut domains = Vec::new(); + let mut results = Vec::new(); + let mut motives = Vec::new(); + for position in 0..plan.positions.len() { + let domain = self.lean_ty(&plan.positions[position].ty)?; + let result = self.result_type(&plan, position); + let lifted = self.b.lift(result, 1, 0); + motives.push(self.b.lam("__v", EXPLICIT, domain, lifted)); + domains.push(domain); + results.push(result); + } + // The minor premises: block constructors in order, then each + // auxiliary's. + let mut minors = Vec::new(); + for position in 0..plan.positions.len() { + for (ctor, shape) in plan.positions[position].constructors.iter().enumerate() { + let mut frees = Vec::new(); + let mut types = Vec::new(); + for field in &shape.fields { + frees.push(self.b.free()); + types.push(self.lean_ty(field)?); + } + let mut hypotheses: Vec> = Vec::new(); + let mut hypothesis_types = Vec::new(); + for recursion in &shape.recursive { + match recursion { + Some(target) => { + hypotheses.push(Some(self.b.free())); + hypothesis_types.push(Some(results[*target])); + } + None => { + hypotheses.push(None); + hypothesis_types.push(None); + } + } + } + let body = + self.constructor_encoding(&plan, position, ctor, &frees, &hypotheses)?; + let mut minor = body; + for (index, hypothesis) in hypotheses.iter().enumerate().rev() { + if let (Some(free), Some(ty)) = (hypothesis, hypothesis_types[index]) { + minor = self.b.lam_over(&format!("__ih{index}"), EXPLICIT, ty, *free, minor); + } + } + for (index, free) in frees.iter().enumerate().rev() { + minor = self.b.lam_over( + &format!("__x{index}"), + EXPLICIT, + types[index], + *free, + minor, + ); + } + minors.push(minor); + } + } + // The rows: the group's recursor with each position as the major + // premise. + let mut names = Vec::new(); + for position in 0..plan.positions.len() { + let name = row_name(&plan, position)?; + let recursor = self + .b + .konst_at(&plan.recursors[position], plan.levels(one.clone())); + let free = self.b.free(); + let mut args = params.clone(); + args.extend(motives.iter().copied()); + args.extend(minors.iter().copied()); + args.push(free); + let applied = self.b.apps(recursor, &args); + let value = self.b.lam_over("__v", EXPLICIT, domains[position], free, applied); + let row_ty = self.b.arrow(domains[position], results[position]); + self.push_row( + &name, + RowKind::Definition { compiled: true }, + row_ty, + value, + )?; + names.push(name); + } + // The implementation records: direct recursion by the eliminator of + // each type, calling the records of the others. + let unsafe_names: Vec = names + .iter() + .map(|name| format!("{name}._unsafe_rec")) + .collect(); + for position in 0..plan.positions.len() { + let mut cases_minors = Vec::new(); + for (ctor, shape) in plan.positions[position].constructors.iter().enumerate() { + let mut frees = Vec::new(); + let mut types = Vec::new(); + for field in &shape.fields { + frees.push(self.b.free()); + types.push(self.lean_ty(field)?); + } + let mut calls: Vec> = Vec::new(); + for (index, recursion) in shape.recursive.iter().enumerate() { + match recursion { + Some(target) => { + let head = self.own(&unsafe_names[*target]); + calls.push(Some(self.b.app(head, frees[index]))); + } + None => calls.push(None), + } + } + let mut minor = self.constructor_encoding(&plan, position, ctor, &frees, &calls)?; + for (index, free) in frees.iter().enumerate().rev() { + minor = self.b.lam_over( + &format!("__x{index}"), + EXPLICIT, + types[index], + *free, + minor, + ); + } + cases_minors.push(minor); + } + let free = self.b.free(); + let cases = self.cases_on_const( + &plan.positions[position].ty, + free, + results[position], + &cases_minors, + )?; + let value = self.b.lam_over("__v", EXPLICIT, domains[position], free, cases); + let row_ty = self.b.arrow(domains[position], results[position]); + self.push_row(&unsafe_names[position], RowKind::Partial, row_ty, value)?; + } + self.group_rows(&unsafe_names); + // The bundles of the containers. + for position in 0..plan.positions.len() { + if let PositionKind::Aux(aux) = &plan.positions[position].kind { + self.bundle(&plan, position, aux.kind, aux.nested_index)?; + } + } + Ok(()) + } + + /// `__L_n` or `__O_n`: the container's encoder as the library's bundle. + fn bundle( + &mut self, + plan: &RecPlan, + position: usize, + kind: AuxKind, + nested_index: u64, + ) -> Result<(), String> { + let aux = match &plan.positions[position].kind { + PositionKind::Aux(aux) => aux.clone(), + PositionKind::Block { .. } | PositionKind::Builtin => { + return Err("a bundle of a type that is not a container".to_owned()); + } + }; + let parts = super::super::data::container_parts(&aux.original)?; + match kind { + AuxKind::List => { + let element = parts[0].clone(); + let alpha = self.lean_ty(&element)?; + let elem = self.enc(&element)?; + let items = self.own(&row_name(plan, position)?); + let ty = { + let head = self.lp("ListEnc"); + self.b.app(head, alpha) + }; + let applied = self.apply( + "ListEnc.mk", + &[("α", alpha)], + &[ + Given::Term(elem), + Given::Term(items), + Given::Refl, + Given::Refl, + ], + )?; + self.push_row( + &format!("__L_{nested_index}"), + RowKind::Definition { compiled: false }, + ty, + applied.proof, + ) + } + AuxKind::Option => { + let inner = parts[0].clone(); + let alpha = self.lean_ty(&inner)?; + let elem = self.enc(&inner)?; + let encoder = self.own(&row_name(plan, position)?); + let ty = { + let head = self.lp("OptEnc"); + self.b.app(head, alpha) + }; + let applied = self.apply( + "OptEnc.mk", + &[("α", alpha)], + &[ + Given::Term(elem), + Given::Term(encoder), + Given::Refl, + Given::Refl, + ], + )?; + self.push_row( + &format!("__O_{nested_index}"), + RowKind::Definition { compiled: false }, + ty, + applied.proof, + ) + } + AuxKind::Prod | AuxKind::Except => Ok(()), + } + } +} diff --git a/crates/lexlean/src/production/certificate/data_a/recplan.rs b/crates/lexlean/src/production/certificate/data_a/recplan.rs new file mode 100644 index 000000000..ce41c7be5 --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/recplan.rs @@ -0,0 +1,439 @@ +//! The recursors a structural definition is written with (SPEC.md §17.17). +//! +//! A function that recurses structurally is defined here by the recursor of +//! its argument's type, as the kernel has it: a motive for each type of the +//! recursive group (the mutual block's types, then the containers the kernel +//! nests in them), and a minor premise for each constructor, whose induction +//! hypotheses stand for the recursive calls. This module reads the group out +//! of the layout and the source: its positions, their constructors, and which +//! of a constructor's fields are recursive. + +use std::collections::BTreeSet; + +use super::super::data::{canonical, container_parts, named_member, type_arguments, Aux, AuxKind}; +use super::super::{components, Gen}; +use super::Writer; +use crate::ir::core::CoreLevel; +use crate::ir::semantic::SemanticType; + +/// How a position of a plan is recursed on. +#[derive(Clone)] +pub(super) enum PositionKind { + /// A document type of the recursive block, by its index in the layout. + Block { index: u64 }, + /// A container nested in the block. + Aux(AuxEntry), + /// A type of Lean's own: `Nat` or a list. + Builtin, +} + +/// A nested container of a group. +#[derive(Clone)] +pub(super) struct AuxEntry { + pub(super) original: SemanticType, + pub(super) canonical: SemanticType, + pub(super) kind: AuxKind, + pub(super) nested_index: u64, +} + +/// One constructor of a position. +#[derive(Clone)] +pub(super) struct RecCtor { + /// The closed types of its fields. + pub(super) fields: Vec, + /// Per field, the position it recurses on, when it is recursive. + pub(super) recursive: Vec>, +} + +/// One position of a plan: a type the recursor has a motive for. +#[derive(Clone)] +pub(super) struct Position { + /// The type as the source writes it. + pub(super) ty: SemanticType, + pub(super) kind: PositionKind, + pub(super) constructors: Vec, +} + +/// The recursors of one recursive group. +#[derive(Clone)] +pub(super) struct RecPlan { + /// The type arguments the recursor is applied at. + pub(super) params: Vec, + pub(super) positions: Vec, + /// The constant of the recursor that has a position as its major + /// premise. + pub(super) recursors: Vec, + /// How the recursor's universe parameters are written for a motive into + /// `Sort u`. + pub(super) builtin_list: bool, +} + +impl RecPlan { + /// The universe levels of the recursor at motive sort `u`. + pub(super) fn levels(&self, motive: CoreLevel) -> Vec { + if self.builtin_list { + vec![motive, CoreLevel::Zero] + } else { + vec![motive] + } + } + + /// The position of a type among the plan's, if it has one. + pub(super) fn position_of(&self, gen: &Gen<'_>, ty: &SemanticType) -> Option { + let key = gen.source.type_text(&canonical(ty)); + self.positions.iter().position(|position| { + let own = match &position.kind { + PositionKind::Aux(aux) => &aux.canonical, + PositionKind::Block { .. } | PositionKind::Builtin => &position.ty, + }; + gen.source.type_text(&canonical(own)) == key + }) + } +} + +impl Writer<'_, '_> { + /// The plan for recursing structurally on a value of closed type `ty`. + pub(super) fn rec_plan(&mut self, ty: &SemanticType) -> Result { + match ty { + SemanticType::Nat => Ok(RecPlan { + params: Vec::new(), + positions: vec![Position { + ty: SemanticType::Nat, + kind: PositionKind::Builtin, + constructors: vec![ + RecCtor { + fields: Vec::new(), + recursive: Vec::new(), + }, + RecCtor { + fields: vec![SemanticType::Nat], + recursive: vec![Some(0)], + }, + ], + }], + recursors: vec!["Nat.rec".to_owned()], + builtin_list: false, + }), + SemanticType::List { element } => Ok(RecPlan { + params: vec![element.as_ref().clone()], + positions: vec![Position { + ty: ty.clone(), + kind: PositionKind::Builtin, + constructors: vec![ + RecCtor { + fields: Vec::new(), + recursive: Vec::new(), + }, + RecCtor { + fields: vec![element.as_ref().clone(), ty.clone()], + recursive: vec![None, Some(0)], + }, + ], + }], + recursors: vec!["List.rec".to_owned()], + builtin_list: true, + }), + SemanticType::Named { .. } => { + let text = self.gen.source.type_text(ty); + let index = *self + .gen + .adts + .get(&text) + .ok_or_else(|| format!("`{text}` has no ADT in the layout"))?; + let graph = self.gen.adt_graph()?; + let component = components(&graph) + .into_iter() + .find(|component| component.contains(&index)) + .ok_or("a document type outside every component")?; + self.group_plan(&component) + } + SemanticType::Type + | SemanticType::Parameter { name: _ } + | SemanticType::Bool + | SemanticType::Prop + | SemanticType::Unit + | SemanticType::Int + | SemanticType::Int8 + | SemanticType::Int16 + | SemanticType::Int32 + | SemanticType::Int64 + | SemanticType::UInt8 + | SemanticType::UInt16 + | SemanticType::UInt32 + | SemanticType::UInt64 + | SemanticType::String + | SemanticType::Bytes + | SemanticType::Ordering + | SemanticType::Option { value: _ } + | SemanticType::Result { ok: _, error: _ } + | SemanticType::Product { left: _, right: _ } + | SemanticType::Function { + parameters: _, + result: _, + } + | SemanticType::Map { key: _, value: _ } + | SemanticType::Set { element: _ } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure => Err(format!( + "structural recursion on `{}` has no recursor here", + self.gen.source.type_text(ty) + )), + } + } + + /// The plan of one component of the document types' graph. + pub(super) fn group_plan(&mut self, component: &[u64]) -> Result { + let mut ordered = Vec::new(); + for &index in component { + let ty = &self.gen.layout.adts[index as usize].ty; + let member = named_member(ty)?; + let module = member.module.clone().ok_or("an unanchored document type")?; + let position = self + .gen + .source + .declaration_position(&module, &member.name) + .ok_or_else(|| format!("`{}` is not declared in `{module}`", member.name))?; + ordered.push((position, index)); + } + ordered.sort_unstable(); + let block: Vec = ordered.into_iter().map(|(_, index)| index).collect(); + let key = |gen: &Gen<'_>, ty: &SemanticType| gen.source.type_text(&canonical(ty)); + let block_keys: BTreeSet = block + .iter() + .map(|&index| key(self.gen, &self.gen.layout.adts[index as usize].ty)) + .collect(); + let mut aux: Vec = Vec::new(); + let mut seen: BTreeSet = BTreeSet::new(); + for &index in &block { + let shape = self + .gen + .source + .document(&self.gen.layout.adts[index as usize].ty)?; + for fields in &shape.fields { + for field in fields { + self.register_aux(field, &block_keys, &mut aux, &mut seen)?; + } + } + } + let mut next = 0; + while next < aux.len() { + for field in self.aux_field_types(&aux[next])? { + self.register_aux(&field, &block_keys, &mut aux, &mut seen)?; + } + next += 1; + } + let first = self.gen.layout.adts[block[0] as usize].ty.clone(); + let first_member = named_member(&first)?; + let first_name = self.document_name(&first_member)?; + let mut positions = Vec::new(); + let mut recursors = Vec::new(); + for &index in &block { + positions.push(Position { + ty: self.gen.layout.adts[index as usize].ty.clone(), + kind: PositionKind::Block { index }, + constructors: Vec::new(), + }); + let member = named_member(&self.gen.layout.adts[index as usize].ty)?; + recursors.push(format!("{}.rec", self.document_name(&member)?)); + } + for (position, entry) in aux.iter().enumerate() { + positions.push(Position { + ty: entry.canonical.clone(), + kind: PositionKind::Aux(AuxEntry { + original: entry.original.clone(), + canonical: entry.canonical.clone(), + kind: entry.kind, + nested_index: entry.nested_index, + }), + constructors: Vec::new(), + }); + recursors.push(format!("{first_name}.rec_{}", position + 1)); + } + let mut plan = RecPlan { + params: type_arguments(&first), + positions, + recursors, + builtin_list: false, + }; + // The constructors, now that every position is known. + for position in 0..plan.positions.len() { + let constructors = self.constructors_of(&plan, position)?; + plan.positions[position].constructors = constructors; + } + for nested in self.gen.nested.values() { + if component.contains(&nested.group) + && !aux.iter().any(|entry| entry.nested_index == nested.index) + { + return Err(format!( + "`{}` is an auxiliary of the group but the kernel nests no such type", + self.gen.source.type_text(&nested.ty) + )); + } + } + Ok(plan) + } + + /// The constructors of a position of a plan. + fn constructors_of(&self, plan: &RecPlan, position: usize) -> Result, String> { + let ctor = |fields: Vec| -> RecCtor { + let recursive = fields + .iter() + .map(|field| plan.position_of(self.gen, field)) + .collect(); + RecCtor { fields, recursive } + }; + let entry = &plan.positions[position]; + match &entry.kind { + PositionKind::Block { index: _ } => { + let shape = self.gen.source.document(&entry.ty)?; + Ok(shape.fields.iter().map(|fields| ctor(fields.clone())).collect()) + } + PositionKind::Builtin => Ok(entry.constructors.clone()), + PositionKind::Aux(aux) => { + let parts = container_parts(&aux.original)?; + Ok(match aux.kind { + AuxKind::List => vec![ + ctor(Vec::new()), + ctor(vec![parts[0].clone(), aux.original.clone()]), + ], + AuxKind::Option => vec![ctor(Vec::new()), ctor(vec![parts[0].clone()])], + AuxKind::Prod => vec![ctor(parts)], + AuxKind::Except => { + vec![ctor(vec![parts[0].clone()]), ctor(vec![parts[1].clone()])] + } + }) + } + } + } + + /// The field types of an auxiliary's constructors other than itself. + fn aux_field_types(&self, aux: &Aux) -> Result, String> { + let parts = container_parts(&aux.original)?; + let expected = match aux.kind { + AuxKind::List | AuxKind::Option => 1, + AuxKind::Prod | AuxKind::Except => 2, + }; + if parts.len() != expected { + return Err(format!( + "`{}` is an auxiliary of the wrong shape", + self.gen.source.type_text(&aux.original) + )); + } + Ok(parts) + } + + /// Whether a canonical type mentions a type of the block. + fn mentions_block(&self, ty: &SemanticType, block_keys: &BTreeSet) -> bool { + match ty { + SemanticType::Named { + member: _, + arguments: _, + } => block_keys.contains(&self.gen.source.type_text(ty)), + SemanticType::List { element } | SemanticType::Option { value: element } => { + self.mentions_block(element, block_keys) + } + SemanticType::Product { left, right } + | SemanticType::Result { + ok: left, + error: right, + } => self.mentions_block(left, block_keys) || self.mentions_block(right, block_keys), + SemanticType::Set { element: _ } + | SemanticType::Map { key: _, value: _ } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure + | SemanticType::Function { + parameters: _, + result: _, + } + | SemanticType::Type + | SemanticType::Prop + | SemanticType::Parameter { name: _ } + | SemanticType::Nat + | SemanticType::Bool + | SemanticType::Unit + | SemanticType::Int + | SemanticType::Int8 + | SemanticType::Int16 + | SemanticType::Int32 + | SemanticType::Int64 + | SemanticType::UInt8 + | SemanticType::UInt16 + | SemanticType::UInt32 + | SemanticType::UInt64 + | SemanticType::String + | SemanticType::Bytes + | SemanticType::Ordering => false, + } + } + + /// Register the container `original` as an auxiliary of the group when it + /// nests a type of the block and is not yet registered. + fn register_aux( + &self, + original: &SemanticType, + block_keys: &BTreeSet, + aux: &mut Vec, + seen: &mut BTreeSet, + ) -> Result<(), String> { + let canonical = canonical(original); + if !self.mentions_block(&canonical, block_keys) { + return Ok(()); + } + let kind = match &canonical { + SemanticType::List { element: _ } => AuxKind::List, + SemanticType::Option { value: _ } => AuxKind::Option, + SemanticType::Product { left: _, right: _ } => AuxKind::Prod, + SemanticType::Result { ok: _, error: _ } => AuxKind::Except, + SemanticType::Named { + member: _, + arguments: _, + } + | SemanticType::Set { element: _ } + | SemanticType::Map { key: _, value: _ } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure + | SemanticType::Function { + parameters: _, + result: _, + } + | SemanticType::Type + | SemanticType::Prop + | SemanticType::Parameter { name: _ } + | SemanticType::Nat + | SemanticType::Bool + | SemanticType::Unit + | SemanticType::Int + | SemanticType::Int8 + | SemanticType::Int16 + | SemanticType::Int32 + | SemanticType::Int64 + | SemanticType::UInt8 + | SemanticType::UInt16 + | SemanticType::UInt32 + | SemanticType::UInt64 + | SemanticType::String + | SemanticType::Bytes + | SemanticType::Ordering => return Ok(()), + }; + let key = self.gen.source.type_text(&canonical); + if !seen.insert(key) { + return Ok(()); + } + let text = self.gen.source.type_text(original); + let nested_index = self + .gen + .nested + .get(&text) + .map(|nested| nested.index) + .ok_or_else(|| { + format!("`{text}` is nested in a recursive group, but certificate A has no auxiliary encoder for it") + })?; + aux.push(Aux { + original: original.clone(), + canonical, + kind, + nested_index, + }); + Ok(()) + } +} diff --git a/crates/lexlean/src/production/certificate/data_a/rows.rs b/crates/lexlean/src/production/certificate/data_a/rows.rs index decd796e5..ee0f3eb81 100644 --- a/crates/lexlean/src/production/certificate/data_a/rows.rs +++ b/crates/lexlean/src/production/certificate/data_a/rows.rs @@ -478,14 +478,6 @@ impl<'a> Writer<'_, 'a> { Ok(self.b.apps(head, &args)) } - /// The encoders of every document type: none, until a program has one. - pub(super) fn encoders(&mut self) -> Result<(), String> { - if self.gen.layout.adts.is_empty() { - return Ok(()); - } - Err("document types".to_owned()) - } - /// `denote`, `root`, and the boundary. pub(super) fn root( &mut self, diff --git a/crates/lexlean/src/production/certificate/data_a/src.rs b/crates/lexlean/src/production/certificate/data_a/src.rs index 00d33206b..ac2d0837e 100644 --- a/crates/lexlean/src/production/certificate/data_a/src.rs +++ b/crates/lexlean/src/production/certificate/data_a/src.rs @@ -550,6 +550,37 @@ impl Writer<'_, '_> { } (self.b.konst_at(&format!("{owner}.casesOn"), vec![level]), params) } + SemanticType::Product { left, right } => { + let left = self.lean_ty(left)?; + let right = self.lean_ty(right)?; + ( + self.b.konst_at( + "Prod.casesOn", + vec![level, CoreLevel::Zero, CoreLevel::Zero], + ), + vec![left, right], + ) + } + SemanticType::Map { key, value } => { + let key = self.lean_ty(key)?; + let value = self.lean_ty(value)?; + let entry = self.b.pair_ty(key, value); + ( + self.b + .konst_at("List.casesOn", vec![level, CoreLevel::Zero]), + vec![entry], + ) + } + SemanticType::ContractViolation | SemanticType::ReasoningFailure => { + let bool = self.b.konst("Bool"); + ( + self.b.konst_at( + "Prod.casesOn", + vec![level, CoreLevel::Zero, CoreLevel::Zero], + ), + vec![bool, bool], + ) + } SemanticType::Type | SemanticType::Parameter { name: _ } | SemanticType::Prop @@ -566,14 +597,10 @@ impl Writer<'_, '_> { | SemanticType::String | SemanticType::Bytes | SemanticType::Ordering - | SemanticType::Product { left: _, right: _ } | SemanticType::Function { parameters: _, result: _, - } - | SemanticType::Map { key: _, value: _ } - | SemanticType::ContractViolation - | SemanticType::ReasoningFailure => { + } => { return Err(format!( "a match on `{}` has no eliminator here", self.gen.source.type_text(scrutinee_ty) From 68720c08c62fd5371b5d1233cf8d5e5db5fc9905 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 16:45:17 +0000 Subject: [PATCH 06/41] #89 stage 3: certificate A data: structural recursion by the argument type's recursor --- .../src/production/certificate/data_a.rs | 1 + .../src/production/certificate/data_a/rows.rs | 12 + .../certificate/data_a/structural.rs | 728 ++++++++++++++++++ crates/lexlean/src/production/kernel/ext.rs | 74 ++ crates/lexlean/src/production/preserve.rs | 13 +- 5 files changed, 827 insertions(+), 1 deletion(-) create mode 100644 crates/lexlean/src/production/certificate/data_a/structural.rs diff --git a/crates/lexlean/src/production/certificate/data_a.rs b/crates/lexlean/src/production/certificate/data_a.rs index 7282e670c..ecff6504e 100644 --- a/crates/lexlean/src/production/certificate/data_a.rs +++ b/crates/lexlean/src/production/certificate/data_a.rs @@ -42,6 +42,7 @@ mod prove; mod recplan; mod rows; mod src; +mod structural; mod templates; pub(super) const LIB: &str = "LexLeanPreservation"; diff --git a/crates/lexlean/src/production/certificate/data_a/rows.rs b/crates/lexlean/src/production/certificate/data_a/rows.rs index ee0f3eb81..2accc35e7 100644 --- a/crates/lexlean/src/production/certificate/data_a/rows.rs +++ b/crates/lexlean/src/production/certificate/data_a/rows.rs @@ -327,6 +327,18 @@ impl<'a> Writer<'_, 'a> { || graph .get(&component[0]) .is_some_and(|edges| edges.contains(&component[0])); + let structural = component.iter().any(|index| { + matches!( + self.gen.subjects.get(index), + Some(Subject::Definition { + recursion: Recursion::Structural { .. }, + .. + }) + ) + }); + if structural { + return self.structural_component(component); + } for index in component { let subject = self.gen.subject_of(*index)?; match subject { diff --git a/crates/lexlean/src/production/certificate/data_a/structural.rs b/crates/lexlean/src/production/certificate/data_a/structural.rs new file mode 100644 index 000000000..ab16c04d5 --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/structural.rs @@ -0,0 +1,728 @@ +//! Functions that recurse structurally (SPEC.md §17.17.4): the width +//! predicate and relation theorem of each are defined by the recursor of the +//! argument's type, its induction hypotheses standing for the recursive calls, +//! and the predicate has an implementation record for the code generator. + +use std::collections::{BTreeMap, HashMap}; + +use super::recplan::RecPlan; +use super::rows::{Binder, LocalSet, Sig}; +use super::{LocalTerm, Pf, RowKind, Scope, Writer, EXPLICIT}; +use crate::calculus::Expr; +use crate::ir::core::CoreLevel; +use crate::ir::semantic::{SemanticTerm, SemanticType}; +use crate::production::kernel::{Given, Term}; +use crate::production::source::{Local, Site}; +use super::super::{CLocal, Ctx, Gen, Hypotheses, Recursion, Subject}; + +/// One function of a structurally recursive group. +struct Member<'a> { + index: u64, + module: String, + name: String, + type_arguments: Vec, + result: SemanticType, + body: &'a SemanticTerm, + site: Site, + type_parameters: Vec<(String, SemanticType)>, + argument: String, + locals: Vec, + /// The position of the recursive argument among the locals. + arg_position: usize, + /// The position in the plan its argument's type has. + plan_position: usize, + /// The binder index of the argument among the width predicate's binders + /// and the relation's. + fits_arg: usize, + rel_arg: usize, + set: LocalSet, + sig: Sig, +} + +/// The hypotheses of one minor premise: for each recursive field of a +/// constructor, the free variable of the field and its hypotheses for the +/// width predicate and the relation, with the position recursed on. +struct Hypothesis { + field: Term, + fits: Term, + rel: Term, + position: usize, +} + +impl<'a> Writer<'_, 'a> { + /// A strongly connected component of structurally recursive functions. + #[allow(clippy::too_many_lines)] + pub(super) fn structural_component(&mut self, component: &[u64]) -> Result<(), String> { + // The members, with the signatures every body is proved against. + let mut members: Vec> = Vec::new(); + for index in component { + let subject = self.gen.subject_of(*index)?; + let Subject::Definition { + module, + name, + type_arguments, + locals, + result, + body, + recursion, + site, + type_parameters, + } = subject + else { + return Err(format!("function {index} is not a definition")); + }; + let Recursion::Structural { argument, mutual: _ } = recursion else { + return Err(format!("`{module}.{name}` is not structurally recursive")); + }; + let clocals = Gen::scope(&locals, ""); + let arg_position = clocals + .iter() + .position(|local| local.local.name == argument) + .ok_or_else(|| format!("`{argument}` is not a parameter of `{name}`"))?; + let set = self.local_set(*index, &clocals)?; + let denotation = self.denotation_of(&module, &name, &type_arguments, &set)?; + let sig = self.signature(*index, &clocals, &set, &result, denotation)?; + self.sigs.insert(*index, sig.clone()); + let width = |local: &CLocal, rel: bool| -> usize { + if local.companion.is_some() { + if rel { + 5 + } else { + 2 + } + } else { + 1 + } + }; + let fits_arg = clocals[..arg_position].iter().map(|l| width(l, false)).sum(); + let rel_arg = clocals[..arg_position].iter().map(|l| width(l, true)).sum(); + members.push(Member { + index: *index, + module, + name, + type_arguments, + result, + body, + site, + type_parameters, + argument, + locals: clocals, + arg_position, + plan_position: 0, + fits_arg, + rel_arg, + set, + sig, + }); + } + let first_type = members[0].locals[members[0].arg_position].local.ty.clone(); + let plan = self.rec_plan(&first_type)?; + for member in &mut members { + let ty = member.locals[member.arg_position].local.ty.clone(); + member.plan_position = plan + .position_of(self.gen, &ty) + .ok_or_else(|| format!("`{}` recurses on a type outside its group", member.name))?; + } + let mut at_position: Vec> = vec![None; plan.positions.len()]; + for (slot, member) in members.iter().enumerate() { + if at_position[member.plan_position].replace(slot).is_some() { + return Err(format!( + "two functions of a group recurse on the same type (`{}`)", + member.name + )); + } + } + let one = CoreLevel::Succ(Box::new(CoreLevel::Zero)); + // The motives: a function of the argument into the statement for every + // other argument. + let bool = self.b.konst("Bool"); + let mut fits_motives = Vec::new(); + let mut rel_motives = Vec::new(); + let mut domains = Vec::new(); + for position in 0..plan.positions.len() { + let domain = self.lean_ty(&plan.positions[position].ty)?; + domains.push(domain); + let member = &members[at_position[position] + .ok_or("a nested type of the group has no function recursing on it")?]; + let others_fits: Vec = member + .sig + .fits_binders + .iter() + .enumerate() + .filter(|(slot, _)| *slot != member.fits_arg) + .map(|(_, binder)| binder.clone()) + .collect(); + let others_rel: Vec = member + .sig + .rel_binders + .iter() + .enumerate() + .filter(|(slot, _)| *slot != member.rel_arg) + .map(|(_, binder)| binder.clone()) + .collect(); + let fits_body = self.pis(&others_fits, bool); + let rel_body = self.pis(&others_rel, member.sig.statement); + // The motive is a function of the argument: its body is closed + // over the member's own argument binder, which it replaces. + let arg_free = member.sig.fits_binders[member.fits_arg].free; + let fits_lifted = self.b.lam_over("__v", EXPLICIT, domain, arg_free, fits_body); + let rel_arg_free = member.sig.rel_binders[member.rel_arg].free; + let rel_lifted = self.b.lam_over("__v", EXPLICIT, domain, rel_arg_free, rel_body); + fits_motives.push(fits_lifted); + rel_motives.push(rel_lifted); + } + // The minor premises, position by position, constructor by + // constructor. + let mut fits_minors = Vec::new(); + let mut rel_minors = Vec::new(); + let mut direct_minors: Vec> = vec![Vec::new(); plan.positions.len()]; + for position in 0..plan.positions.len() { + let slot = at_position[position].ok_or("a position without a function")?; + for ctor in 0..plan.positions[position].constructors.len() { + let (fits_minor, rel_minor, direct) = + self.structural_minor(&plan, &members, &at_position, slot, ctor)?; + fits_minors.push(fits_minor); + rel_minors.push(rel_minor); + direct_minors[position].push(direct); + } + } + let mut params = Vec::new(); + for argument in &plan.params { + params.push(self.lean_ty(argument)?); + } + // The rows of each member. + for member in &members { + let position = member.plan_position; + let recursor = self + .b + .konst_at(&plan.recursors[position], plan.levels(one.clone())); + // The predicate. + let mut fits_args = params.clone(); + fits_args.extend(fits_motives.iter().copied()); + fits_args.extend(fits_minors.iter().copied()); + fits_args.push(member.sig.fits_binders[member.fits_arg].free); + let applied = self.b.apps(recursor, &fits_args); + let others: Vec = member + .sig + .fits_binders + .iter() + .enumerate() + .filter(|(slot, _)| *slot != member.fits_arg) + .map(|(_, binder)| binder.free) + .collect(); + let body = self.b.apps(applied, &others); + let value = self.lambdas(&member.sig.fits_binders, body); + let row_ty = self.pis(&member.sig.fits_binders, bool); + self.push_row( + &format!("__fits_{}", member.index), + RowKind::Definition { compiled: true }, + row_ty, + value, + )?; + } + for member in &members { + let position = member.plan_position; + let recursor = self + .b + .konst_at(&plan.recursors[position], plan.levels(CoreLevel::Zero)); + let mut rel_args = params.clone(); + rel_args.extend(rel_motives.iter().copied()); + rel_args.extend(rel_minors.iter().copied()); + rel_args.push(member.sig.rel_binders[member.rel_arg].free); + let applied = self.b.apps(recursor, &rel_args); + let others: Vec = member + .sig + .rel_binders + .iter() + .enumerate() + .filter(|(slot, _)| *slot != member.rel_arg) + .map(|(_, binder)| binder.free) + .collect(); + let body = self.b.apps(applied, &others); + let value = self.lambdas(&member.sig.rel_binders, body); + let row_ty = self.pis(&member.sig.rel_binders, member.sig.statement); + self.push_row( + &format!("__rel_{}", member.index), + RowKind::Theorem, + row_ty, + value, + )?; + } + // The implementation records of the predicates, by direct recursion. + let unsafe_names: Vec = members + .iter() + .map(|member| format!("__fits_{}._unsafe_rec", member.index)) + .collect(); + for member in &members { + let position = member.plan_position; + let discriminant = member.sig.fits_binders[member.fits_arg].free; + let ty = plan.positions[position].ty.clone(); + let minors = direct_minors[position].clone(); + let cases = self.cases_on_fresh_motive(&ty, discriminant, bool, &minors)?; + let value = self.lambdas(&member.sig.fits_binders, cases); + let row_ty = self.pis(&member.sig.fits_binders, bool); + self.push_row( + &format!("__fits_{}._unsafe_rec", member.index), + RowKind::Partial, + row_ty, + value, + )?; + } + self.group_rows(&unsafe_names); + Ok(()) + } + + /// `T.casesOn` at the constant motive `result` whose minors are the + /// functions of the constructor's fields. + fn cases_on_fresh_motive( + &mut self, + ty: &SemanticType, + discriminant: Term, + result: Term, + minors: &[Term], + ) -> Result { + self.cases_on_const(ty, discriminant, result, minors) + } + + /// The denotation of a definition instance applied to its locals. + fn denotation_of( + &mut self, + module: &str, + name: &str, + type_arguments: &[SemanticType], + set: &LocalSet, + ) -> Result { + let lean_name = self.gen.source.lean_name( + module, + &crate::ir::semantic::MemberRef { + module: Some(module.to_owned()), + name: name.to_owned(), + }, + ); + let head = self.b.konst(&lean_name); + let mut args = Vec::new(); + for argument in type_arguments { + args.push(self.lean_ty(argument)?); + } + args.extend(set.terms.iter().map(|terms| terms.value)); + Ok(self.b.apps(head, &args)) + } + + /// The minor premises of constructor `ctor` of the position function + /// `slot` recurses on: for the width predicate, for the relation, and its + /// direct form for the implementation record. + #[allow(clippy::too_many_lines)] + fn structural_minor( + &mut self, + plan: &RecPlan, + members: &[Member<'a>], + at_position: &[Option], + slot: usize, + ctor: usize, + ) -> Result<(Term, Term, Term), String> { + let member = &members[slot]; + let shape = &plan.positions[member.plan_position].constructors[ctor]; + // The branch of the source for this constructor. + let SemanticTerm::Match { + scrutinee, + branches, + } = member.body + else { + return Err(format!( + "structural `{}.{}` is not a top-level match", + member.module, member.name + )); + }; + match scrutinee.as_ref() { + SemanticTerm::Var { name } if *name == member.argument => {} + _ => { + return Err(format!( + "structural `{}.{}` does not match on `{}`", + member.module, member.name, member.argument + )); + } + } + let argument_ty = member.locals[member.arg_position].local.ty.clone(); + let argument_generic = member.locals[member.arg_position].generic.clone(); + let generic_site = Site { + module: member.site.module.clone(), + substitution: BTreeMap::new(), + }; + let mut chosen = None; + for (branch_index, branch) in branches.iter().enumerate() { + let (constructor, fields) = self.gen.source.branch(branch, &argument_ty, &member.site)?; + let position = self.constructor_position(&constructor)?; + if position == ctor { + chosen = Some((branch_index, branch, constructor, fields)); + break; + } + } + let (branch_index, branch, constructor, fields) = chosen.ok_or_else(|| { + format!( + "structural `{}.{}` has no branch for constructor {ctor}", + member.module, member.name + ) + })?; + let (_, generic_fields) = + self.gen + .source + .branch(branch, &argument_generic, &generic_site)?; + // Free variables: the fields, the hypotheses, and the other arguments. + let mut field_frees = Vec::new(); + let mut field_types = Vec::new(); + for ty in &shape.fields { + field_frees.push(self.b.free()); + field_types.push(self.lean_ty(ty)?); + } + let mut hypotheses: Vec = Vec::new(); + let mut hypothesis_info: Vec<(Term, Term, Term, Term)> = Vec::new(); + for (field_index, recursion) in shape.recursive.iter().enumerate() { + if let Some(target) = recursion { + let target_member = &members[at_position[*target] + .ok_or("a recursive field whose type has no function")?]; + let fits = self.b.free(); + let rel = self.b.free(); + hypotheses.push(Hypothesis { + field: field_frees[field_index], + fits, + rel, + position: *target, + }); + // Their types: the motive of the target at the field. + let others_fits: Vec = target_member + .sig + .fits_binders + .iter() + .enumerate() + .filter(|(slot, _)| *slot != target_member.fits_arg) + .map(|(_, binder)| binder.clone()) + .collect(); + let others_rel: Vec = target_member + .sig + .rel_binders + .iter() + .enumerate() + .filter(|(slot, _)| *slot != target_member.rel_arg) + .map(|(_, binder)| binder.clone()) + .collect(); + let bool = self.b.konst("Bool"); + let fits_body = self.pis(&others_fits, bool); + let rel_body = self.pis(&others_rel, target_member.sig.statement); + let at_fits = target_member.sig.fits_binders[target_member.fits_arg].free; + let at_rel = target_member.sig.rel_binders[target_member.rel_arg].free; + let fits_ty = self + .b + .substitute(fits_body, &[at_fits], &[field_frees[field_index]]); + let rel_ty = self + .b + .substitute(rel_body, &[at_rel], &[field_frees[field_index]]); + hypothesis_info.push((fits, rel, fits_ty, rel_ty)); + } + } + // The other arguments of this minor are fresh. + let fresh = self.local_set(member.index, &member.locals)?; + // The value of the constructor on the fields. + let constructed = self.constructor_value_of( + &constructor, + &argument_ty, + &branch.constructor, + &member.site, + &field_frees, + )?; + // The scope: every local but the argument, then the fields. + let mut ctx_locals: Vec = Vec::new(); + let mut terms: Vec = Vec::new(); + for (position, local) in member.locals.iter().enumerate() { + if position != member.arg_position { + ctx_locals.push(local.clone()); + terms.push(fresh.terms[position]); + } + } + for ((name, (ty, generic)), free) in branch + .binders + .iter() + .zip(fields.into_iter().zip(generic_fields)) + .zip(&field_frees) + { + ctx_locals.push(CLocal { + local: Local { + name: name.clone(), + ty, + }, + generic, + companion: None, + }); + terms.push(LocalTerm { + value: *free, + companion: None, + }); + } + // The environment: the parameters bound to the encodings. + let parameters = self.gen.program.functions[member.index as usize] + .parameters + .clone(); + let mut encoded = Vec::new(); + for (position, local) in member.locals.iter().enumerate() { + if position == member.arg_position { + let enc = self.enc(&argument_ty)?; + encoded.push(self.b.app(enc, constructed)); + } else { + encoded.push(self.encoded_local(local, fresh.terms[position])?); + } + } + let empty = self.empty_env(); + let env = self.extend_env(empty, ¶meters, &encoded)?; + let scope = Scope { + ctx: Ctx::new( + member.site.clone(), + member.type_parameters.clone(), + ctx_locals, + Hypotheses::new(), + member.index, + ), + terms, + env, + }; + let program = self.gen.program; + let top = &program.functions[member.index as usize].body; + let (match_ty, scrutinee_expr, arms) = match top { + Expr::Match { + ty, + scrutinee, + arms, + } => (ty, scrutinee.as_ref(), arms.as_slice()), + _ => return Err("the lowering of a structural body is not a match".to_owned()), + }; + let arm = arms + .get(branch_index) + .ok_or("a structural match lowered to fewer arms")?; + let arm_fields: Vec = { + let mut out = Vec::new(); + for (field_ty, free) in shape.fields.iter().zip(&field_frees) { + let enc = self.enc(field_ty)?; + out.push(self.b.app(enc, *free)); + } + out + }; + let arm_env = self.extend_env(env, &arm.binders, &arm_fields)?; + let arm_scope = Scope { + env: arm_env, + ..scope.clone() + }; + let body_pf = self.prove(&branch.body, &arm.body, &arm_scope)?; + let scrutinee_encoded = { + let enc = self.enc(&argument_ty)?; + self.b.app(enc, constructed) + }; + let mut chain = self.arm_hit( + env, + scrutinee_encoded, + arms, + branch_index, + &arm_fields, + arm_env, + body_pf, + )?; + for earlier in (0..branch_index).rev() { + chain = self.arm_miss(env, scrutinee_encoded, arms, earlier, chain)?; + } + let (p, outer) = (self.prog, env); + let number = match scrutinee_expr { + Expr::Var { name } => *name, + _ => return Err("the lowering of a structural scrutinee is not a variable".to_owned()), + }; + let x = self.nat_u(number); + let tested = self.apply( + "conv_var", + &[("p", p), ("env", outer), ("x", x), ("v", scrutinee_encoded)], + &[Given::Refl], + )?; + let arms_term = self.arms_list(arms)?; + let match_ty_term = self.ty_term(match_ty); + let matched = self.apply( + "conv_match", + &[("p", p), ("env", outer), ("t", match_ty_term), ("arms", arms_term)], + &[ + Given::Proof(tested.proof, tested.ty), + Given::Proof(chain.proof, chain.ty), + ], + )?; + // The statement at this constructor. + let mut from: Vec = member.sig.rel_params.clone(); + let mut to: Vec = Vec::new(); + for (slot_index, param) in member.sig.rel_params.iter().enumerate() { + let _ = param; + if slot_index == member.rel_arg { + to.push(constructed); + } else { + to.push(fresh.rel[slot_index].free); + } + } + // `from` and `to` pair the outer binders with this minor's. + let _ = &mut from; + let statement = self.b.substitute(member.sig.statement, &from, &to); + let (_, parts) = self.b.spine(statement); + let observation = parts.get(3).copied().ok_or("a statement with no observation")?; + let arguments = parts.get(2).copied().ok_or("a statement with no arguments")?; + let function = self.function_term(&program.functions[member.index as usize])?; + let fi = self.nat_u(member.index); + let funrel = self.apply( + "funRel_intro", + &[ + ("p", p), + ("fi", fi), + ("args", arguments), + ("o", observation), + ("fn", function), + ("env", env), + ], + &[ + Given::Refl, + Given::Refl, + Given::Proof(matched.proof, matched.ty), + ], + )?; + let fits_term = self.fits_of(matched)?; + // Replace the recursive calls by the hypotheses. + let mut fits_calls: HashMap = HashMap::new(); + for (slot_index, other) in members.iter().enumerate() { + fits_calls.insert(other.index as usize, (slot_index, other.fits_arg)); + } + let hypothesis_fits: HashMap = hypotheses + .iter() + .map(|hypothesis| (hypothesis.field, hypothesis.fits)) + .collect(); + let hypothesis_rel: HashMap = hypotheses + .iter() + .map(|hypothesis| (hypothesis.field, hypothesis.rel)) + .collect(); + let _ = hypothesis_info.len(); + let fits_names: HashMap = members + .iter() + .map(|other| (format!("{}.__fits_{}", self.name, other.index), other.fits_arg)) + .collect(); + let rel_names: HashMap = members + .iter() + .map(|other| (format!("{}.__rel_{}", self.name, other.index), other.rel_arg)) + .collect(); + let unsafe_map: HashMap = members + .iter() + .map(|other| { + ( + format!("{}.__fits_{}", self.name, other.index), + self.b.konst(&format!( + "{}.__fits_{}._unsafe_rec", + self.name, other.index + )), + ) + }) + .collect(); + let rewrite_with = |b: &mut crate::production::kernel::Builder, + names: &HashMap, + by_field: &HashMap, + name: &str, + args: &[Term]| + -> Option { + let &arg_slot = names.get(name)?; + let field = *args.get(arg_slot)?; + let hypothesis = *by_field.get(&field)?; + let rest: Vec = args + .iter() + .enumerate() + .filter(|(position, _)| *position != arg_slot) + .map(|(_, arg)| *arg) + .collect(); + Some(b.apps(hypothesis, &rest)) + }; + let fits_rec = self.b.rewrite_apps(fits_term, &mut |b, name, args| { + rewrite_with(b, &fits_names, &hypothesis_fits, name, args) + }); + let rel_rec = self.b.rewrite_apps(funrel.proof, &mut |b, name, args| { + rewrite_with(b, &rel_names, &hypothesis_rel, name, args) + }); + let others_fits_fresh: Vec = fresh + .fits + .iter() + .enumerate() + .filter(|(slot_index, _)| *slot_index != member.fits_arg) + .map(|(_, binder)| binder.free) + .collect(); + let others_rel_fresh: Vec = fresh + .rel + .iter() + .enumerate() + .filter(|(slot_index, _)| *slot_index != member.rel_arg) + .map(|(_, binder)| binder.free) + .collect(); + let _ = (others_fits_fresh, others_rel_fresh); + // The minors: fields, then hypotheses, then the other arguments. + let wrap = |writer: &mut Self, + body: Term, + binders: &[Binder], + arg_slot: usize, + ihs: &[(Term, Term)]| + -> Term { + let mut out = body; + for (index, binder) in binders.iter().enumerate().rev() { + if index != arg_slot { + out = writer + .b + .lam_over(&binder.name, EXPLICIT, binder.ty, binder.free, out); + } + } + for (free, ty) in ihs.iter().rev() { + out = writer.b.lam_over("__ih", EXPLICIT, *ty, *free, out); + } + for (index, free) in field_frees.iter().enumerate().rev() { + out = writer + .b + .lam_over(&format!("__x{index}"), EXPLICIT, field_types[index], *free, out); + } + out + }; + let fits_ihs: Vec<(Term, Term)> = hypothesis_info + .iter() + .map(|(fits, _, fits_ty, _)| (*fits, *fits_ty)) + .collect(); + let rel_ihs: Vec<(Term, Term)> = hypothesis_info + .iter() + .map(|(_, rel, _, rel_ty)| (*rel, *rel_ty)) + .collect(); + let fits_minor = wrap(self, fits_rec, &fresh.fits, member.fits_arg, &fits_ihs); + let rel_minor = wrap(self, rel_rec, &fresh.rel, member.rel_arg, &rel_ihs); + // The direct form: the fresh arguments are the outer ones, and a + // recursive call is a call of the implementation record. + let direct_body = self.b.rewrite_apps(fits_term, &mut |b, name, args| { + let target = unsafe_map.get(name)?; + Some(b.apps(*target, args)) + }); + let outer_fits: Vec = member.sig.fits_binders.iter().map(|b| b.free).collect(); + let fresh_fits: Vec = fresh.fits.iter().map(|b| b.free).collect(); + let mut from_fits = fresh_fits.clone(); + let mut to_fits = outer_fits.clone(); + // The argument is the constructor over the fields, not an outer + // binder. + from_fits.remove(member.fits_arg); + to_fits.remove(member.fits_arg); + let direct_body = self.b.substitute(direct_body, &from_fits, &to_fits); + let mut direct = direct_body; + for (index, free) in field_frees.iter().enumerate().rev() { + direct = self + .b + .lam_over(&format!("__x{index}"), EXPLICIT, field_types[index], *free, direct); + } + Ok((fits_minor, rel_minor, direct)) + } + + /// The value of a constructor on the free variables of its fields. + fn constructor_value_of( + &mut self, + constructor: &crate::production::source::Constructor, + ty: &SemanticType, + member: &crate::ir::semantic::MemberRef, + site: &Site, + frees: &[Term], + ) -> Result { + self.constructed(constructor, ty, member, site, frees) + } +} diff --git a/crates/lexlean/src/production/kernel/ext.rs b/crates/lexlean/src/production/kernel/ext.rs index 86536e190..0fe35f1b4 100644 --- a/crates/lexlean/src/production/kernel/ext.rs +++ b/crates/lexlean/src/production/kernel/ext.rs @@ -270,6 +270,80 @@ impl Builder { }) } + /// `term` with each application of a constant rewritten by `rewrite`, + /// which is given the constant's name and its (already rewritten) + /// arguments and returns the replacement, or `None` to keep the + /// application. + pub fn rewrite_apps( + &mut self, + term: Term, + rewrite: &mut dyn FnMut(&mut Builder, &str, &[Term]) -> Option, + ) -> Term { + let mut memo = HashMap::new(); + self.rewrite_memo(term, rewrite, &mut memo) + } + + fn rewrite_memo( + &mut self, + term: Term, + rewrite: &mut dyn FnMut(&mut Builder, &str, &[Term]) -> Option, + memo: &mut HashMap, + ) -> Term { + if let Some(&done) = memo.get(&term) { + return done; + } + let out = match self.nodes[term].clone() { + CoreNode::BVar { .. } + | CoreNode::Sort { .. } + | CoreNode::Nat { .. } + | CoreNode::String { .. } => term, + CoreNode::Const { n, .. } => rewrite(self, &n, &[]).unwrap_or(term), + CoreNode::App { .. } => { + let (head, args) = self.spine(term); + let args: Vec = args + .iter() + .map(|arg| self.rewrite_memo(*arg, rewrite, memo)) + .collect(); + match self.nodes[head].clone() { + CoreNode::Const { n, .. } => match rewrite(self, &n, &args) { + Some(replaced) => replaced, + None => self.apps(head, &args), + }, + _ => { + let head = self.rewrite_memo(head, rewrite, memo); + self.apps(head, &args) + } + } + } + CoreNode::Lambda { n, b, t, v } => { + let t = self.rewrite_memo(t, rewrite, memo); + let v = self.rewrite_memo(v, rewrite, memo); + self.lam(&n, b, t, v) + } + CoreNode::Forall { n, b, t, v } => { + let t = self.rewrite_memo(t, rewrite, memo); + let v = self.rewrite_memo(v, rewrite, memo); + self.pi(&n, b, t, v) + } + CoreNode::Let { n, t, v, body, d } => { + let t = self.rewrite_memo(t, rewrite, memo); + let v = self.rewrite_memo(v, rewrite, memo); + let body = self.rewrite_memo(body, rewrite, memo); + self.let_node(&n, t, v, body, d) + } + CoreNode::Mdata { m, x } => { + let x = self.rewrite_memo(x, rewrite, memo); + self.mdata_node(m, x) + } + CoreNode::Projection { n, s, i, x } => { + let x = self.rewrite_memo(x, rewrite, memo); + self.projection(&n, &s, i, x) + } + }; + memo.insert(term, out); + out + } + /// `term` with each node of `from` replaced by the node of `to` at its /// position, wherever it occurs (a free variable, in practice). pub fn substitute(&mut self, term: Term, from: &[Term], to: &[Term]) -> Term { diff --git a/crates/lexlean/src/production/preserve.rs b/crates/lexlean/src/production/preserve.rs index 46fb27349..3b08d6c4c 100644 --- a/crates/lexlean/src/production/preserve.rs +++ b/crates/lexlean/src/production/preserve.rs @@ -470,7 +470,18 @@ pub fn audit_core( row.name, row.kind )); } - if row.safety.is_some() { + // The only definitions that are not safe are the compiler's + // implementation records of a recursive definition of the module, which + // the differential evaluator runs: `f._unsafe_rec`, partial, beside the + // safe `f` the theorems are about. No safe declaration can name one, so + // no theorem depends on it. + let record = row.safety == Some(crate::ir::core::CoreSafety::Partial) + && matches!(row.kind, CoreDeclKind::Definition) + && row + .name + .strip_suffix("._unsafe_rec") + .is_some_and(|safe| module.declarations.iter().any(|other| other.name == safe)); + if row.safety.is_some() && !record { return Err(format!("the row `{}` is not a safe definition", row.name)); } if !matches!(row.policy, CoreAxiomPolicy::Exact(_)) { From 556839c55931d9f1e08e642937b63510bbc6c625 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 16:54:03 +0000 Subject: [PATCH 07/41] #89 stage 3: certificate A data: mutual structural recursion on one type packs the group --- .../src/production/certificate/data_a.rs | 16 +- .../production/certificate/data_a/encoders.rs | 1 + .../certificate/data_a/structural.rs | 744 ++++++++++++------ 3 files changed, 505 insertions(+), 256 deletions(-) diff --git a/crates/lexlean/src/production/certificate/data_a.rs b/crates/lexlean/src/production/certificate/data_a.rs index ecff6504e..5648a718e 100644 --- a/crates/lexlean/src/production/certificate/data_a.rs +++ b/crates/lexlean/src/production/certificate/data_a.rs @@ -332,6 +332,14 @@ impl<'g, 'a> Writer<'g, 'a> { value: Term, ) -> Result<(), String> { let full = format!("{}.{name}", self.name); + if std::env::var("LEXLEAN_SHOW").is_ok_and(|wanted| full.ends_with(&wanted)) { + let text = format!("{full} : {}\n:= {}\n", self.b.show(ty), self.b.show(value)); + if let Ok(path) = std::env::var("LEXLEAN_SHOW_FILE") { + let _ = std::fs::write(path, text); + } else { + eprintln!("{text}"); + } + } if self.b.has_placeholder(ty) || self.b.has_placeholder(value) { return Err(format!( "the row `{full}` of certificate A has a free variable or placeholder left" @@ -342,14 +350,6 @@ impl<'g, 'a> Writer<'g, 'a> { "the row `{full}` of certificate A has a loose bound variable" )); } - if std::env::var("LEXLEAN_SHOW").is_ok_and(|wanted| full.ends_with(&wanted)) { - let text = format!("{full} : {}\n:= {}\n", self.b.show(ty), self.b.show(value)); - if let Ok(path) = std::env::var("LEXLEAN_SHOW_FILE") { - let _ = std::fs::write(path, text); - } else { - eprintln!("{text}"); - } - } let axioms = self.row_axiom_set(&[ty, value])?; self.row_axioms.insert(full.clone(), axioms.clone()); self.rows.push(Row { diff --git a/crates/lexlean/src/production/certificate/data_a/encoders.rs b/crates/lexlean/src/production/certificate/data_a/encoders.rs index 179d9bcf0..784622dbb 100644 --- a/crates/lexlean/src/production/certificate/data_a/encoders.rs +++ b/crates/lexlean/src/production/certificate/data_a/encoders.rs @@ -304,6 +304,7 @@ impl<'a> Writer<'_, 'a> { )?; names.push(name); } + self.group_rows(&names); // The implementation records: direct recursion by the eliminator of // each type, calling the records of the others. let unsafe_names: Vec = names diff --git a/crates/lexlean/src/production/certificate/data_a/structural.rs b/crates/lexlean/src/production/certificate/data_a/structural.rs index ab16c04d5..ab41d4f6e 100644 --- a/crates/lexlean/src/production/certificate/data_a/structural.rs +++ b/crates/lexlean/src/production/certificate/data_a/structural.rs @@ -123,67 +123,67 @@ impl<'a> Writer<'_, 'a> { .position_of(self.gen, &ty) .ok_or_else(|| format!("`{}` recurses on a type outside its group", member.name))?; } - let mut at_position: Vec> = vec![None; plan.positions.len()]; + let mut at_position: Vec> = vec![Vec::new(); plan.positions.len()]; for (slot, member) in members.iter().enumerate() { - if at_position[member.plan_position].replace(slot).is_some() { - return Err(format!( - "two functions of a group recurse on the same type (`{}`)", - member.name - )); - } + at_position[member.plan_position].push(slot); } let one = CoreLevel::Succ(Box::new(CoreLevel::Zero)); - // The motives: a function of the argument into the statement for every - // other argument. let bool = self.b.konst("Bool"); + // The motives: a function of the argument into the statements of the + // functions that recurse on its type, each for every other argument. + // Two functions on one type share a motive, which packs their + // statements as a pair. let mut fits_motives = Vec::new(); let mut rel_motives = Vec::new(); let mut domains = Vec::new(); for position in 0..plan.positions.len() { let domain = self.lean_ty(&plan.positions[position].ty)?; domains.push(domain); - let member = &members[at_position[position] - .ok_or("a nested type of the group has no function recursing on it")?]; - let others_fits: Vec = member - .sig - .fits_binders - .iter() - .enumerate() - .filter(|(slot, _)| *slot != member.fits_arg) - .map(|(_, binder)| binder.clone()) - .collect(); - let others_rel: Vec = member - .sig - .rel_binders - .iter() - .enumerate() - .filter(|(slot, _)| *slot != member.rel_arg) - .map(|(_, binder)| binder.clone()) - .collect(); - let fits_body = self.pis(&others_fits, bool); - let rel_body = self.pis(&others_rel, member.sig.statement); - // The motive is a function of the argument: its body is closed - // over the member's own argument binder, which it replaces. - let arg_free = member.sig.fits_binders[member.fits_arg].free; - let fits_lifted = self.b.lam_over("__v", EXPLICIT, domain, arg_free, fits_body); - let rel_arg_free = member.sig.rel_binders[member.rel_arg].free; - let rel_lifted = self.b.lam_over("__v", EXPLICIT, domain, rel_arg_free, rel_body); - fits_motives.push(fits_lifted); - rel_motives.push(rel_lifted); + if at_position[position].is_empty() { + return Err("a nested type of the group has no function recursing on it".to_owned()); + } + let fits_tys = self.component_types(&members, &at_position[position], false)?; + let rel_tys = self.component_types(&members, &at_position[position], true)?; + let fits_body = self.pack_types(&fits_tys, false); + let rel_body = self.pack_types(&rel_tys, true); + let first = &members[at_position[position][0]]; + // The motive is a function of the argument: each statement is + // closed over its own member's argument binder, which the common + // bound variable replaces. + let mut fits_body = fits_body; + let mut rel_body = rel_body; + let bound = self.b.free(); + for slot in &at_position[position] { + let member = &members[*slot]; + fits_body = self.b.substitute( + fits_body, + &[member.sig.fits_binders[member.fits_arg].free], + &[bound], + ); + rel_body = self.b.substitute( + rel_body, + &[member.sig.rel_binders[member.rel_arg].free], + &[bound], + ); + } + let _ = first; + fits_motives.push(self.b.lam_over("__v", EXPLICIT, domain, bound, fits_body)); + rel_motives.push(self.b.lam_over("__v", EXPLICIT, domain, bound, rel_body)); } // The minor premises, position by position, constructor by // constructor. let mut fits_minors = Vec::new(); let mut rel_minors = Vec::new(); - let mut direct_minors: Vec> = vec![Vec::new(); plan.positions.len()]; + let mut direct_minors: Vec> = vec![Vec::new(); members.len()]; for position in 0..plan.positions.len() { - let slot = at_position[position].ok_or("a position without a function")?; for ctor in 0..plan.positions[position].constructors.len() { let (fits_minor, rel_minor, direct) = - self.structural_minor(&plan, &members, &at_position, slot, ctor)?; + self.structural_minors(&plan, &members, &at_position, position, ctor)?; fits_minors.push(fits_minor); rel_minors.push(rel_minor); - direct_minors[position].push(direct); + for (slot, term) in direct { + direct_minors[slot].push(term); + } } } let mut params = Vec::new(); @@ -191,17 +191,29 @@ impl<'a> Writer<'_, 'a> { params.push(self.lean_ty(argument)?); } // The rows of each member. + let mut fits_names = Vec::new(); for member in &members { let position = member.plan_position; + let siblings = &at_position[position]; + let index = siblings + .iter() + .position(|slot| members[*slot].index == member.index) + .ok_or("a member outside its position")?; let recursor = self .b .konst_at(&plan.recursors[position], plan.levels(one.clone())); - // The predicate. let mut fits_args = params.clone(); fits_args.extend(fits_motives.iter().copied()); fits_args.extend(fits_minors.iter().copied()); fits_args.push(member.sig.fits_binders[member.fits_arg].free); let applied = self.b.apps(recursor, &fits_args); + let fits_tys = self.component_types_at( + &members, + siblings, + false, + member.sig.fits_binders[member.fits_arg].free, + )?; + let selected = self.unpack(&fits_tys, index, applied, false); let others: Vec = member .sig .fits_binders @@ -210,9 +222,10 @@ impl<'a> Writer<'_, 'a> { .filter(|(slot, _)| *slot != member.fits_arg) .map(|(_, binder)| binder.free) .collect(); - let body = self.b.apps(applied, &others); + let body = self.b.apps(selected, &others); let value = self.lambdas(&member.sig.fits_binders, body); let row_ty = self.pis(&member.sig.fits_binders, bool); + fits_names.push(format!("__fits_{}", member.index)); self.push_row( &format!("__fits_{}", member.index), RowKind::Definition { compiled: true }, @@ -220,8 +233,14 @@ impl<'a> Writer<'_, 'a> { value, )?; } + self.group_rows(&fits_names); for member in &members { let position = member.plan_position; + let siblings = &at_position[position]; + let index = siblings + .iter() + .position(|slot| members[*slot].index == member.index) + .ok_or("a member outside its position")?; let recursor = self .b .konst_at(&plan.recursors[position], plan.levels(CoreLevel::Zero)); @@ -230,6 +249,13 @@ impl<'a> Writer<'_, 'a> { rel_args.extend(rel_minors.iter().copied()); rel_args.push(member.sig.rel_binders[member.rel_arg].free); let applied = self.b.apps(recursor, &rel_args); + let rel_tys = self.component_types_at( + &members, + siblings, + true, + member.sig.rel_binders[member.rel_arg].free, + )?; + let selected = self.unpack(&rel_tys, index, applied, true); let others: Vec = member .sig .rel_binders @@ -238,7 +264,7 @@ impl<'a> Writer<'_, 'a> { .filter(|(slot, _)| *slot != member.rel_arg) .map(|(_, binder)| binder.free) .collect(); - let body = self.b.apps(applied, &others); + let body = self.b.apps(selected, &others); let value = self.lambdas(&member.sig.rel_binders, body); let row_ty = self.pis(&member.sig.rel_binders, member.sig.statement); self.push_row( @@ -253,12 +279,12 @@ impl<'a> Writer<'_, 'a> { .iter() .map(|member| format!("__fits_{}._unsafe_rec", member.index)) .collect(); - for member in &members { + for (slot, member) in members.iter().enumerate() { let position = member.plan_position; let discriminant = member.sig.fits_binders[member.fits_arg].free; let ty = plan.positions[position].ty.clone(); - let minors = direct_minors[position].clone(); - let cases = self.cases_on_fresh_motive(&ty, discriminant, bool, &minors)?; + let minors = direct_minors[slot].clone(); + let cases = self.cases_on_const(&ty, discriminant, bool, &minors)?; let value = self.lambdas(&member.sig.fits_binders, cases); let row_ty = self.pis(&member.sig.fits_binders, bool); self.push_row( @@ -272,6 +298,109 @@ impl<'a> Writer<'_, 'a> { Ok(()) } + /// The statements of the functions at a position, over the position's + /// argument: the type of the other arguments to the result. + fn component_types( + &mut self, + members: &[Member<'a>], + slots: &[usize], + relation: bool, + ) -> Result, String> { + let bool = self.b.konst("Bool"); + let mut out = Vec::new(); + for slot in slots { + let member = &members[*slot]; + if relation { + let others: Vec = member + .sig + .rel_binders + .iter() + .enumerate() + .filter(|(position, _)| *position != member.rel_arg) + .map(|(_, binder)| binder.clone()) + .collect(); + out.push(self.pis(&others, member.sig.statement)); + } else { + let others: Vec = member + .sig + .fits_binders + .iter() + .enumerate() + .filter(|(position, _)| *position != member.fits_arg) + .map(|(_, binder)| binder.clone()) + .collect(); + out.push(self.pis(&others, bool)); + } + } + Ok(out) + } + + /// The types of a position's functions as one: the pair type + /// `PProd a (PProd b c)`, or for statements `a ∧ b ∧ c`. + fn pack_types(&mut self, tys: &[Term], relation: bool) -> Term { + let Some((first, rest)) = tys.split_first() else { + return self.b.konst("PUnit"); + }; + if rest.is_empty() { + return *first; + } + let packed = self.pack_types(rest, relation); + if relation { + let and = self.b.konst("And"); + self.b.apps(and, &[*first, packed]) + } else { + let one = CoreLevel::Succ(Box::new(CoreLevel::Zero)); + let head = self.b.konst_at("PProd", vec![one.clone(), one]); + self.b.apps(head, &[*first, packed]) + } + } + + /// The pair of values of types `tys`. + fn pack_values(&mut self, tys: &[Term], values: &[Term], relation: bool) -> Term { + let (first, rest) = (values[0], &values[1..]); + if rest.is_empty() { + return first; + } + let packed_ty = self.pack_types(&tys[1..], relation); + let packed = self.pack_values(&tys[1..], rest, relation); + if relation { + let intro = self.b.konst("And.intro"); + self.b.apps(intro, &[tys[0], packed_ty, first, packed]) + } else { + let one = CoreLevel::Succ(Box::new(CoreLevel::Zero)); + let head = self.b.konst_at("PProd.mk", vec![one.clone(), one]); + self.b.apps(head, &[tys[0], packed_ty, first, packed]) + } + } + + /// The component `index` of a packed value of types `tys`. + fn unpack(&mut self, tys: &[Term], index: usize, packed: Term, relation: bool) -> Term { + if tys.len() == 1 { + return packed; + } + let rest_ty = self.pack_types(&tys[1..], relation); + if index == 0 { + if relation { + let head = self.b.konst("And.left"); + self.b.apps(head, &[tys[0], rest_ty, packed]) + } else { + let one = CoreLevel::Succ(Box::new(CoreLevel::Zero)); + let head = self.b.konst_at("PProd.fst", vec![one.clone(), one]); + self.b.apps(head, &[tys[0], rest_ty, packed]) + } + } else { + let rest = if relation { + let head = self.b.konst("And.right"); + self.b.apps(head, &[tys[0], rest_ty, packed]) + } else { + let one = CoreLevel::Succ(Box::new(CoreLevel::Zero)); + let head = self.b.konst_at("PProd.snd", vec![one.clone(), one]); + self.b.apps(head, &[tys[0], rest_ty, packed]) + }; + self.unpack(&tys[1..], index - 1, rest, relation) + } + } + /// `T.casesOn` at the constant motive `result` whose minors are the /// functions of the constructor's fields. fn cases_on_fresh_motive( @@ -308,18 +437,148 @@ impl<'a> Writer<'_, 'a> { Ok(self.b.apps(head, &args)) } - /// The minor premises of constructor `ctor` of the position function - /// `slot` recurses on: for the width predicate, for the relation, and its - /// direct form for the implementation record. + /// The component types of a position's functions with the argument + /// replaced by `value`. + fn component_types_at( + &mut self, + members: &[Member<'a>], + slots: &[usize], + relation: bool, + value: Term, + ) -> Result, String> { + let tys = self.component_types(members, slots, relation)?; + let mut out = Vec::new(); + for (ty, slot) in tys.into_iter().zip(slots) { + let member = &members[*slot]; + let at = if relation { + member.sig.rel_binders[member.rel_arg].free + } else { + member.sig.fits_binders[member.fits_arg].free + }; + out.push(self.b.substitute(ty, &[at], &[value])); + } + Ok(out) + } + + /// The minor premises of constructor `ctor` of a position of the plan: + /// for the width predicates, for the relations (each packed over the + /// functions at the position), and the direct form of each function's. #[allow(clippy::too_many_lines)] - fn structural_minor( + fn structural_minors( &mut self, plan: &RecPlan, members: &[Member<'a>], - at_position: &[Option], + at_position: &[Vec], + position: usize, + ctor: usize, + ) -> Result<(Term, Term, Vec<(usize, Term)>), String> { + let shape = &plan.positions[position].constructors[ctor]; + let slots = &at_position[position]; + // Free variables: the fields, and a hypothesis for each recursive + // field. + let mut field_frees = Vec::new(); + let mut field_types = Vec::new(); + for ty in &shape.fields { + field_frees.push(self.b.free()); + field_types.push(self.lean_ty(ty)?); + } + let mut hypotheses: Vec = Vec::new(); + let mut hypothesis_types: Vec<(Term, Term)> = Vec::new(); + for (field_index, recursion) in shape.recursive.iter().enumerate() { + if let Some(target) = recursion { + let fits = self.b.free(); + let rel = self.b.free(); + hypotheses.push(Hypothesis { + field: field_frees[field_index], + fits, + rel, + position: *target, + }); + let fits_tys = self.component_types_at( + members, + &at_position[*target], + false, + field_frees[field_index], + )?; + let rel_tys = self.component_types_at( + members, + &at_position[*target], + true, + field_frees[field_index], + )?; + hypothesis_types.push(( + self.pack_types(&fits_tys, false), + self.pack_types(&rel_tys, true), + )); + } + } + let mut fits_inners = Vec::new(); + let mut rel_inners = Vec::new(); + let mut directs = Vec::new(); + let mut constructed_value = None; + for slot in slots { + let (fits_inner, rel_inner, direct, constructed) = self.structural_member_minor( + plan, + members, + at_position, + *slot, + ctor, + &field_frees, + &field_types, + &hypotheses, + )?; + fits_inners.push(fits_inner); + rel_inners.push(rel_inner); + directs.push((*slot, direct)); + constructed_value.get_or_insert(constructed); + } + let constructed = constructed_value.ok_or("a position with no function")?; + let fits_tys = self.component_types_at(members, slots, false, constructed)?; + let rel_tys = self.component_types_at(members, slots, true, constructed)?; + let fits_body = self.pack_values(&fits_tys, &fits_inners, false); + let rel_body = self.pack_values(&rel_tys, &rel_inners, true); + let wrap = |writer: &mut Self, body: Term, relation: bool| -> Term { + let mut out = body; + for (hypothesis, tys) in hypotheses.iter().zip(&hypothesis_types).rev() { + let (free, ty) = if relation { + (hypothesis.rel, tys.1) + } else { + (hypothesis.fits, tys.0) + }; + out = writer.b.lam_over("__ih", EXPLICIT, ty, free, out); + } + for (index, free) in field_frees.iter().enumerate().rev() { + out = writer.b.lam_over( + &format!("__x{index}"), + EXPLICIT, + field_types[index], + *free, + out, + ); + } + out + }; + let fits_minor = wrap(self, fits_body, false); + let rel_minor = wrap(self, rel_body, true); + Ok((fits_minor, rel_minor, directs)) + } + + /// One function's body at a constructor: the width predicate and the + /// relation, each abstracted over the other arguments, and the direct + /// form of the predicate over the fields; with the value of the + /// constructor. + #[allow(clippy::too_many_lines)] + fn structural_member_minor( + &mut self, + plan: &RecPlan, + members: &[Member<'a>], + at_position: &[Vec], slot: usize, ctor: usize, - ) -> Result<(Term, Term, Term), String> { + field_frees: &[Term], + field_types: &[Term], + hypotheses: &[Hypothesis], + ) -> Result<(Term, Term, Term, Term), String> { let member = &members[slot]; let shape = &plan.positions[member.plan_position].constructors[ctor]; // The branch of the source for this constructor. @@ -350,9 +609,11 @@ impl<'a> Writer<'_, 'a> { }; let mut chosen = None; for (branch_index, branch) in branches.iter().enumerate() { - let (constructor, fields) = self.gen.source.branch(branch, &argument_ty, &member.site)?; - let position = self.constructor_position(&constructor)?; - if position == ctor { + let (constructor, fields) = + self.gen + .source + .branch(branch, &argument_ty, &member.site)?; + if self.constructor_position(&constructor)? == ctor { chosen = Some((branch_index, branch, constructor, fields)); break; } @@ -367,67 +628,14 @@ impl<'a> Writer<'_, 'a> { self.gen .source .branch(branch, &argument_generic, &generic_site)?; - // Free variables: the fields, the hypotheses, and the other arguments. - let mut field_frees = Vec::new(); - let mut field_types = Vec::new(); - for ty in &shape.fields { - field_frees.push(self.b.free()); - field_types.push(self.lean_ty(ty)?); - } - let mut hypotheses: Vec = Vec::new(); - let mut hypothesis_info: Vec<(Term, Term, Term, Term)> = Vec::new(); - for (field_index, recursion) in shape.recursive.iter().enumerate() { - if let Some(target) = recursion { - let target_member = &members[at_position[*target] - .ok_or("a recursive field whose type has no function")?]; - let fits = self.b.free(); - let rel = self.b.free(); - hypotheses.push(Hypothesis { - field: field_frees[field_index], - fits, - rel, - position: *target, - }); - // Their types: the motive of the target at the field. - let others_fits: Vec = target_member - .sig - .fits_binders - .iter() - .enumerate() - .filter(|(slot, _)| *slot != target_member.fits_arg) - .map(|(_, binder)| binder.clone()) - .collect(); - let others_rel: Vec = target_member - .sig - .rel_binders - .iter() - .enumerate() - .filter(|(slot, _)| *slot != target_member.rel_arg) - .map(|(_, binder)| binder.clone()) - .collect(); - let bool = self.b.konst("Bool"); - let fits_body = self.pis(&others_fits, bool); - let rel_body = self.pis(&others_rel, target_member.sig.statement); - let at_fits = target_member.sig.fits_binders[target_member.fits_arg].free; - let at_rel = target_member.sig.rel_binders[target_member.rel_arg].free; - let fits_ty = self - .b - .substitute(fits_body, &[at_fits], &[field_frees[field_index]]); - let rel_ty = self - .b - .substitute(rel_body, &[at_rel], &[field_frees[field_index]]); - hypothesis_info.push((fits, rel, fits_ty, rel_ty)); - } - } // The other arguments of this minor are fresh. let fresh = self.local_set(member.index, &member.locals)?; - // The value of the constructor on the fields. - let constructed = self.constructor_value_of( + let constructed = self.constructed( &constructor, &argument_ty, &branch.constructor, &member.site, - &field_frees, + field_frees, )?; // The scope: every local but the argument, then the fields. let mut ctx_locals: Vec = Vec::new(); @@ -442,7 +650,7 @@ impl<'a> Writer<'_, 'a> { .binders .iter() .zip(fields.into_iter().zip(generic_fields)) - .zip(&field_frees) + .zip(field_frees) { ctx_locals.push(CLocal { local: Local { @@ -458,9 +666,8 @@ impl<'a> Writer<'_, 'a> { }); } // The environment: the parameters bound to the encodings. - let parameters = self.gen.program.functions[member.index as usize] - .parameters - .clone(); + let program = self.gen.program; + let parameters = program.functions[member.index as usize].parameters.clone(); let mut encoded = Vec::new(); for (position, local) in member.locals.iter().enumerate() { if position == member.arg_position { @@ -483,7 +690,6 @@ impl<'a> Writer<'_, 'a> { terms, env, }; - let program = self.gen.program; let top = &program.functions[member.index as usize].body; let (match_ty, scrutinee_expr, arms) = match top { Expr::Match { @@ -496,14 +702,11 @@ impl<'a> Writer<'_, 'a> { let arm = arms .get(branch_index) .ok_or("a structural match lowered to fewer arms")?; - let arm_fields: Vec = { - let mut out = Vec::new(); - for (field_ty, free) in shape.fields.iter().zip(&field_frees) { - let enc = self.enc(field_ty)?; - out.push(self.b.app(enc, *free)); - } - out - }; + let mut arm_fields = Vec::new(); + for (field_ty, free) in shape.fields.iter().zip(field_frees) { + let enc = self.enc(field_ty)?; + arm_fields.push(self.b.app(enc, *free)); + } let arm_env = self.extend_env(env, &arm.binders, &arm_fields)?; let arm_scope = Scope { env: arm_env, @@ -526,7 +729,7 @@ impl<'a> Writer<'_, 'a> { for earlier in (0..branch_index).rev() { chain = self.arm_miss(env, scrutinee_encoded, arms, earlier, chain)?; } - let (p, outer) = (self.prog, env); + let p = self.prog; let number = match scrutinee_expr { Expr::Var { name } => *name, _ => return Err("the lowering of a structural scrutinee is not a variable".to_owned()), @@ -534,33 +737,32 @@ impl<'a> Writer<'_, 'a> { let x = self.nat_u(number); let tested = self.apply( "conv_var", - &[("p", p), ("env", outer), ("x", x), ("v", scrutinee_encoded)], + &[("p", p), ("env", env), ("x", x), ("v", scrutinee_encoded)], &[Given::Refl], )?; let arms_term = self.arms_list(arms)?; let match_ty_term = self.ty_term(match_ty); let matched = self.apply( "conv_match", - &[("p", p), ("env", outer), ("t", match_ty_term), ("arms", arms_term)], + &[("p", p), ("env", env), ("t", match_ty_term), ("arms", arms_term)], &[ Given::Proof(tested.proof, tested.ty), Given::Proof(chain.proof, chain.ty), ], )?; // The statement at this constructor. - let mut from: Vec = member.sig.rel_params.clone(); - let mut to: Vec = Vec::new(); - for (slot_index, param) in member.sig.rel_params.iter().enumerate() { - let _ = param; - if slot_index == member.rel_arg { - to.push(constructed); - } else { - to.push(fresh.rel[slot_index].free); - } - } - // `from` and `to` pair the outer binders with this minor's. - let _ = &mut from; - let statement = self.b.substitute(member.sig.statement, &from, &to); + let to: Vec = (0..member.sig.rel_params.len()) + .map(|slot_index| { + if slot_index == member.rel_arg { + constructed + } else { + fresh.rel[slot_index].free + } + }) + .collect(); + let statement = self + .b + .substitute(member.sig.statement, &member.sig.rel_params, &to); let (_, parts) = self.b.spine(statement); let observation = parts.get(3).copied().ok_or("a statement with no observation")?; let arguments = parts.get(2).copied().ok_or("a statement with no arguments")?; @@ -583,85 +785,97 @@ impl<'a> Writer<'_, 'a> { ], )?; let fits_term = self.fits_of(matched)?; - // Replace the recursive calls by the hypotheses. - let mut fits_calls: HashMap = HashMap::new(); - for (slot_index, other) in members.iter().enumerate() { - fits_calls.insert(other.index as usize, (slot_index, other.fits_arg)); - } - let hypothesis_fits: HashMap = hypotheses - .iter() - .map(|hypothesis| (hypothesis.field, hypothesis.fits)) - .collect(); - let hypothesis_rel: HashMap = hypotheses + // The recursive calls become the hypotheses (unpacked to the function + // called) applied to the other arguments of the call. + let by_field: HashMap = hypotheses .iter() - .map(|hypothesis| (hypothesis.field, hypothesis.rel)) + .map(|hypothesis| (hypothesis.field, hypothesis)) .collect(); - let _ = hypothesis_info.len(); - let fits_names: HashMap = members - .iter() - .map(|other| (format!("{}.__fits_{}", self.name, other.index), other.fits_arg)) - .collect(); - let rel_names: HashMap = members - .iter() - .map(|other| (format!("{}.__rel_{}", self.name, other.index), other.rel_arg)) - .collect(); - let unsafe_map: HashMap = members - .iter() - .map(|other| { - ( + let called: HashMap = { + let mut out = HashMap::new(); + for other in members { + let siblings = &at_position[other.plan_position]; + let index = siblings + .iter() + .position(|slot| members[*slot].index == other.index) + .ok_or("a member outside its position")?; + out.insert( format!("{}.__fits_{}", self.name, other.index), - self.b.konst(&format!( - "{}.__fits_{}._unsafe_rec", - self.name, other.index - )), - ) + (other.fits_arg, other.plan_position, index, false), + ); + out.insert( + format!("{}.__rel_{}", self.name, other.index), + (other.rel_arg, other.plan_position, index, true), + ); + } + out + }; + // The component types of each position, with the free variables that + // stand for the argument of each function there. + let mut tys_cache: HashMap<(usize, bool), (Vec, Vec)> = HashMap::new(); + for position in 0..plan.positions.len() { + for relation in [false, true] { + if at_position[position].is_empty() { + continue; + } + let tys = self.component_types(members, &at_position[position], relation)?; + let frees: Vec = at_position[position] + .iter() + .map(|slot| { + let other = &members[*slot]; + if relation { + other.sig.rel_binders[other.rel_arg].free + } else { + other.sig.fits_binders[other.fits_arg].free + } + }) + .collect(); + tys_cache.insert((position, relation), (tys, frees)); + } + } + let mut replace = |writer: &mut Self, term: Term, relation: bool| -> Term { + let called = &called; + let by_field = &by_field; + let tys_cache = &tys_cache; + let mut packers: Vec<(Term, Term)> = Vec::new(); + let mut results: HashMap<(Term, usize), Term> = HashMap::new(); + let _ = (&mut packers, &mut results); + // The unpacking needs the builder, which `rewrite_apps` lends. + let mut projections: Vec<(Term, usize, usize, bool)> = Vec::new(); + let _ = &mut projections; + writer.b.rewrite_apps(term, &mut |b, name, args| { + let &(arg_slot, call_position, index, is_relation) = called.get(name)?; + if is_relation != relation { + return None; + } + let field = *args.get(arg_slot)?; + let hypothesis = by_field.get(&field)?; + if hypothesis.position != call_position { + return None; + } + let packed = if relation { hypothesis.rel } else { hypothesis.fits }; + let (tys, frees) = tys_cache.get(&(call_position, relation))?; + let at: Vec = tys + .iter() + .map(|ty| { + let targets = vec![field; frees.len()]; + b.substitute(*ty, frees, &targets) + }) + .collect(); + let selected = unpack_with(b, &at, index, packed, relation); + let rest: Vec = args + .iter() + .enumerate() + .filter(|(position, _)| *position != arg_slot) + .map(|(_, arg)| *arg) + .collect(); + Some(b.apps(selected, &rest)) }) - .collect(); - let rewrite_with = |b: &mut crate::production::kernel::Builder, - names: &HashMap, - by_field: &HashMap, - name: &str, - args: &[Term]| - -> Option { - let &arg_slot = names.get(name)?; - let field = *args.get(arg_slot)?; - let hypothesis = *by_field.get(&field)?; - let rest: Vec = args - .iter() - .enumerate() - .filter(|(position, _)| *position != arg_slot) - .map(|(_, arg)| *arg) - .collect(); - Some(b.apps(hypothesis, &rest)) }; - let fits_rec = self.b.rewrite_apps(fits_term, &mut |b, name, args| { - rewrite_with(b, &fits_names, &hypothesis_fits, name, args) - }); - let rel_rec = self.b.rewrite_apps(funrel.proof, &mut |b, name, args| { - rewrite_with(b, &rel_names, &hypothesis_rel, name, args) - }); - let others_fits_fresh: Vec = fresh - .fits - .iter() - .enumerate() - .filter(|(slot_index, _)| *slot_index != member.fits_arg) - .map(|(_, binder)| binder.free) - .collect(); - let others_rel_fresh: Vec = fresh - .rel - .iter() - .enumerate() - .filter(|(slot_index, _)| *slot_index != member.rel_arg) - .map(|(_, binder)| binder.free) - .collect(); - let _ = (others_fits_fresh, others_rel_fresh); - // The minors: fields, then hypotheses, then the other arguments. - let wrap = |writer: &mut Self, - body: Term, - binders: &[Binder], - arg_slot: usize, - ihs: &[(Term, Term)]| - -> Term { + let fits_rec = replace(self, fits_term, false); + let rel_rec = replace(self, funrel.proof, true); + // Abstracted over the other arguments, which the motive quantifies. + let wrap_others = |writer: &mut Self, body: Term, binders: &[Binder], arg_slot: usize| { let mut out = body; for (index, binder) in binders.iter().enumerate().rev() { if index != arg_slot { @@ -670,36 +884,28 @@ impl<'a> Writer<'_, 'a> { .lam_over(&binder.name, EXPLICIT, binder.ty, binder.free, out); } } - for (free, ty) in ihs.iter().rev() { - out = writer.b.lam_over("__ih", EXPLICIT, *ty, *free, out); - } - for (index, free) in field_frees.iter().enumerate().rev() { - out = writer - .b - .lam_over(&format!("__x{index}"), EXPLICIT, field_types[index], *free, out); - } out }; - let fits_ihs: Vec<(Term, Term)> = hypothesis_info - .iter() - .map(|(fits, _, fits_ty, _)| (*fits, *fits_ty)) - .collect(); - let rel_ihs: Vec<(Term, Term)> = hypothesis_info - .iter() - .map(|(_, rel, _, rel_ty)| (*rel, *rel_ty)) - .collect(); - let fits_minor = wrap(self, fits_rec, &fresh.fits, member.fits_arg, &fits_ihs); - let rel_minor = wrap(self, rel_rec, &fresh.rel, member.rel_arg, &rel_ihs); + let fits_inner = wrap_others(self, fits_rec, &fresh.fits, member.fits_arg); + let rel_inner = wrap_others(self, rel_rec, &fresh.rel, member.rel_arg); // The direct form: the fresh arguments are the outer ones, and a // recursive call is a call of the implementation record. + let unsafe_map: HashMap = members + .iter() + .map(|other| { + ( + format!("{}.__fits_{}", self.name, other.index), + format!("{}.__fits_{}._unsafe_rec", self.name, other.index), + ) + }) + .collect(); let direct_body = self.b.rewrite_apps(fits_term, &mut |b, name, args| { let target = unsafe_map.get(name)?; - Some(b.apps(*target, args)) + let head = b.konst(target); + Some(b.apps(head, args)) }); - let outer_fits: Vec = member.sig.fits_binders.iter().map(|b| b.free).collect(); - let fresh_fits: Vec = fresh.fits.iter().map(|b| b.free).collect(); - let mut from_fits = fresh_fits.clone(); - let mut to_fits = outer_fits.clone(); + let mut from_fits: Vec = fresh.fits.iter().map(|b| b.free).collect(); + let mut to_fits: Vec = member.sig.fits_binders.iter().map(|b| b.free).collect(); // The argument is the constructor over the fields, not an outer // binder. from_fits.remove(member.fits_arg); @@ -711,18 +917,60 @@ impl<'a> Writer<'_, 'a> { .b .lam_over(&format!("__x{index}"), EXPLICIT, field_types[index], *free, direct); } - Ok((fits_minor, rel_minor, direct)) + Ok((fits_inner, rel_inner, direct, constructed)) } - /// The value of a constructor on the free variables of its fields. - fn constructor_value_of( - &mut self, - constructor: &crate::production::source::Constructor, - ty: &SemanticType, - member: &crate::ir::semantic::MemberRef, - site: &Site, - frees: &[Term], - ) -> Result { - self.constructed(constructor, ty, member, site, frees) +} + +/// The component `index` of a packed value of types `tys`, built on the +/// builder alone. +fn unpack_with( + b: &mut crate::production::kernel::Builder, + tys: &[Term], + index: usize, + packed: Term, + relation: bool, +) -> Term { + if tys.len() == 1 { + return packed; + } + let rest_ty = pack_types_with(b, &tys[1..], relation); + let one = CoreLevel::Succ(Box::new(CoreLevel::Zero)); + if index == 0 { + if relation { + let head = b.konst("And.left"); + b.apps(head, &[tys[0], rest_ty, packed]) + } else { + let head = b.konst_at("PProd.fst", vec![one.clone(), one]); + b.apps(head, &[tys[0], rest_ty, packed]) + } + } else { + let rest = if relation { + let head = b.konst("And.right"); + b.apps(head, &[tys[0], rest_ty, packed]) + } else { + let head = b.konst_at("PProd.snd", vec![one.clone(), one]); + b.apps(head, &[tys[0], rest_ty, packed]) + }; + unpack_with(b, &tys[1..], index - 1, rest, relation) + } +} + +/// The pair type of `tys`, built on the builder alone. +fn pack_types_with(b: &mut crate::production::kernel::Builder, tys: &[Term], relation: bool) -> Term { + let Some((first, rest)) = tys.split_first() else { + return b.konst("PUnit"); + }; + if rest.is_empty() { + return *first; + } + let packed = pack_types_with(b, rest, relation); + if relation { + let and = b.konst("And"); + b.apps(and, &[*first, packed]) + } else { + let one = CoreLevel::Succ(Box::new(CoreLevel::Zero)); + let head = b.konst_at("PProd", vec![one.clone(), one]); + b.apps(head, &[*first, packed]) } } From 944139931823c5572ac4dd7cea2df63837f3a96c Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 17:20:40 +0000 Subject: [PATCH 08/41] #89 stage 3: certificate A data: well-founded recursion by WellFounded.fix with the source's decrease evidence Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- .../src/production/certificate/data_a.rs | 39 +- .../production/certificate/data_a/matching.rs | 115 +++- .../production/certificate/data_a/prove.rs | 106 +++- .../src/production/certificate/data_a/rows.rs | 25 +- .../src/production/certificate/data_a/src.rs | 64 ++- .../certificate/data_a/structural.rs | 34 +- .../src/production/certificate/data_a/wf.rs | 528 ++++++++++++++++++ 7 files changed, 849 insertions(+), 62 deletions(-) create mode 100644 crates/lexlean/src/production/certificate/data_a/wf.rs diff --git a/crates/lexlean/src/production/certificate/data_a.rs b/crates/lexlean/src/production/certificate/data_a.rs index 5648a718e..64aafb7d7 100644 --- a/crates/lexlean/src/production/certificate/data_a.rs +++ b/crates/lexlean/src/production/certificate/data_a.rs @@ -30,7 +30,7 @@ use crate::ir::core::{ CoreAxiomPolicy, CoreBinderInfo, CoreDeclKind, CoreDeclaration, CoreModule, CoreReducibility, CoreSafety, CoreTransparency, CORE_SPEC_2, }; -use crate::ir::semantic::SemanticType; +use crate::ir::semantic::{MemberRef, SemanticTerm, SemanticType, Step}; mod boundary; mod calls; @@ -44,6 +44,7 @@ mod rows; mod src; mod structural; mod templates; +mod wf; pub(super) const LIB: &str = "LexLeanPreservation"; pub(super) const SEM: &str = "LexLeanTarget.TargetSemantics"; @@ -111,6 +112,39 @@ pub(super) struct Scope { pub(super) ctx: Ctx, pub(super) terms: Vec, pub(super) env: Term, + /// The numbered `if` and `match` arms of a well-founded body that enclose + /// the term, outermost first. + pub(super) path: Vec, +} + +/// One enclosing arm of a well-founded body: the free variable standing for +/// the equation the arm was selected by, and for a `match` the free variables +/// of the arm's fields. +#[derive(Clone)] +pub(super) struct PathStep { + pub(super) step: Step, + pub(super) hypothesis: Term, + pub(super) fields: Vec, +} + +/// One recursive call of a well-founded body: the arm it lies in, its +/// arguments, and the theorem that its arguments decrease the measure. +#[derive(Clone)] +pub(super) struct WfSite { + pub(super) path: Vec, + pub(super) arguments: Vec, + pub(super) evidence: MemberRef, +} + +/// The well-founded definition whose relation is being derived. +#[derive(Clone)] +pub(super) struct WfState { + pub(super) index: u64, + pub(super) sites: Vec, + /// The definition's type arguments, as Lean types. + pub(super) type_arguments: Vec, + /// The free variables of the definition's parameters. + pub(super) parameters: Vec, } impl Scope { @@ -145,6 +179,8 @@ pub(super) struct Writer<'g, 'a> { exprs: HashMap<*const Expr, Term>, /// The signature of each function whose relation has been stated. pub(super) sigs: HashMap, + /// The well-founded definition under derivation. + pub(super) wf: Option, } impl<'g, 'a> Writer<'g, 'a> { @@ -176,6 +212,7 @@ impl<'g, 'a> Writer<'g, 'a> { prog, exprs: HashMap::new(), sigs: HashMap::new(), + wf: None, } } diff --git a/crates/lexlean/src/production/certificate/data_a/matching.rs b/crates/lexlean/src/production/certificate/data_a/matching.rs index da5958c7b..be92daf05 100644 --- a/crates/lexlean/src/production/certificate/data_a/matching.rs +++ b/crates/lexlean/src/production/certificate/data_a/matching.rs @@ -5,7 +5,7 @@ use super::{Pf, Scope, Writer, EXPLICIT}; use crate::calculus::{Arm, Expr}; use crate::ir::core::CoreLevel; -use crate::ir::semantic::{SemanticBranch, SemanticTerm, SemanticType}; +use crate::ir::semantic::{SemanticBranch, SemanticTerm, SemanticType, Step}; use crate::production::kernel::{Given, Term}; use crate::production::source::Constructor; @@ -174,6 +174,7 @@ impl<'a> Writer<'_, 'a> { let bound = self.b.free(); let hypothesis = self.b.free(); let bool = self.b.konst("Bool"); + let node = self.node_of(scope, whole); // Per constructor, in declaration order: the arm's width predicate, // value, and derivation, each abstracted over the arm's fields. let mut fits_minors: Vec<(usize, Term)> = Vec::new(); @@ -194,7 +195,19 @@ impl<'a> Writer<'_, 'a> { fields.push(self.b.app(enc, *free)); } let arm_env = self.extend_env(env, &arm.binders, &fields)?; - let inner = self.bind(scope, locals.clone(), &frees, arm_env); + let mut inner = self.bind(scope, locals.clone(), &frees, arm_env); + let equation_free = self.b.free(); + if let Some(node) = node { + inner = Self::entering( + &inner, + Step::Match { + node, + branch: position, + }, + equation_free, + &frees, + ); + } let body_pf = self.prove(&branch.body, &arm.body, &inner)?; let fits = self.fits_of(body_pf)?; let body_src = self.src(&branch.body, &inner)?; @@ -210,16 +223,37 @@ impl<'a> Writer<'_, 'a> { proof = self.arm_miss(env, encoded, arms, earlier, proof)?; } let ctor_position = self.constructor_position(&constructor)?; - let wrap = |writer: &mut Self, term: Term| { - let mut out = term; - for (index, (local, _)) in locals.iter().enumerate().rev() { - out = writer.b.lam_over( - &super::super::identifier(&local.name), - EXPLICIT, + let binders: Vec<(String, Term, Term)> = locals + .iter() + .enumerate() + .map(|(index, (local, _))| { + ( + super::super::identifier(&local.name), types[index], frees[index], - out, - ); + ) + }) + .collect(); + if node.is_some() { + // The arm is taken by an equation the decrease evidence of a + // recursive call within it reads: each of the predicate, the + // value, and the derivation carries it as a hypothesis. + let lean_value = self.constructor_value(&constructor, &scrutinee_ty, branch, &frees, scope)?; + let of_bound = self.eq(lean_scrutinee, bound, lean_value); + let of_scrutinee = self.eq(lean_scrutinee, scrutinee_src, lean_value); + let unused = self.b.free(); + let fits_arm = self.arm_lambda(&binders, (equation_free, of_bound), fits); + let value_arm = self.arm_lambda(&binders, (unused, of_bound), body_src); + let proof_arm = self.arm_lambda(&binders, (equation_free, of_scrutinee), proof.proof); + fits_minors.push((ctor_position, fits_arm)); + value_minors.push((ctor_position, value_arm)); + proof_minors.push((ctor_position, proof_arm)); + continue; + } + let wrap = |writer: &mut Self, term: Term| { + let mut out = term; + for (name, ty, free) in binders.iter().rev() { + out = writer.b.lam_over(name, EXPLICIT, *ty, *free, out); } out }; @@ -231,16 +265,32 @@ impl<'a> Writer<'_, 'a> { minors.sort_by_key(|(position, _)| *position); minors.into_iter().map(|(_, term)| term).collect() }; + let sorted_with = |minors: &Vec<(usize, Term)>| minors.clone(); let (fits_terms, value_terms, proof_terms) = ( - sorted(fits_minors), - sorted(value_minors), - sorted(proof_minors), + sorted(fits_minors.clone()), + sorted(value_minors.clone()), + sorted(proof_minors.clone()), ); // G a = the arms' predicates by cases on a; W a = the encoding of the // arms' values by cases on a. - let g_body = self.cases_on_const(&scrutinee_ty, bound, bool, &fits_terms)?; + let one = CoreLevel::Succ(Box::new(CoreLevel::Zero)); + let g_body = if node.is_some() { + self.equation_cases(&scrutinee_ty, bound, bool, one.clone(), sorted_with(&fits_minors))? + } else { + self.cases_on_const(&scrutinee_ty, bound, bool, &fits_terms)? + }; let g = self.b.lam_over("__a", EXPLICIT, lean_scrutinee, bound, g_body); - let w_inner = self.cases_on_const(&scrutinee_ty, bound, result_lean, &value_terms)?; + let w_inner = if node.is_some() { + self.equation_cases( + &scrutinee_ty, + bound, + result_lean, + one, + sorted_with(&value_minors), + )? + } else { + self.cases_on_const(&scrutinee_ty, bound, result_lean, &value_terms)? + }; let w_body = self.b.app(result_enc, w_inner); let w = self.b.lam_over("__a", EXPLICIT, lean_scrutinee, bound, w_body); // ha : ∀ a, sv = a → ConvA p env (enc a) arms (Rel (G a) (W a)), @@ -252,17 +302,32 @@ impl<'a> Writer<'_, 'a> { let rel = self.rel(g_a, w_a); let conv_a = self.lp("ConvA"); let target = self.b.apps(conv_a, &[p, env, encoded_bound, arms_term, rel]); - let motive = self - .b - .lam_over("__a", EXPLICIT, lean_scrutinee, bound, target); - let cases = self.cases_on( - &scrutinee_ty, - bound, - motive, - CoreLevel::Zero, - &proof_terms, - )?; let equation = self.eq(lean_scrutinee, scrutinee_src, bound); + let cases = if node.is_some() { + let dependent = self.b.arrow(equation, target); + let motive = self + .b + .lam_over("__a", EXPLICIT, lean_scrutinee, bound, dependent); + let cases = self.cases_on( + &scrutinee_ty, + bound, + motive, + CoreLevel::Zero, + &proof_terms, + )?; + self.b.app(cases, hypothesis) + } else { + let motive = self + .b + .lam_over("__a", EXPLICIT, lean_scrutinee, bound, target); + self.cases_on( + &scrutinee_ty, + bound, + motive, + CoreLevel::Zero, + &proof_terms, + )? + }; let with_hypothesis = self .b .lam_over("__h", EXPLICIT, equation, hypothesis, cases); diff --git a/crates/lexlean/src/production/certificate/data_a/prove.rs b/crates/lexlean/src/production/certificate/data_a/prove.rs index aed7a0865..30a72bc48 100644 --- a/crates/lexlean/src/production/certificate/data_a/prove.rs +++ b/crates/lexlean/src/production/certificate/data_a/prove.rs @@ -5,8 +5,10 @@ use super::super::{element_of, Ctx}; use super::{CompTerms, LocalTerm, Pf, Scope, Writer, EXPLICIT}; use crate::calculus::{Arm, Expr, Shape, Ty}; +use crate::ir::core::CoreLevel; use crate::ir::semantic::{ SemanticBranch, SemanticEdge, SemanticMapEntry, SemanticPrimitive, SemanticTerm, SemanticType, + Step, }; use crate::production::kernel::{Given, Term}; use crate::production::source::{Constructor, Local, Source}; @@ -425,7 +427,12 @@ impl<'a> Writer<'_, 'a> { fits_args.extend(passed.fits); } let list = self.operands(env, &proofs)?; - let rel = self.call_relation(index, &relation_args)?; + let recursive = self.wf.as_ref().is_some_and(|state| state.index == index); + let rel = if recursive { + self.wf_call(index, arguments, &relation_args, scope)? + } else { + self.call_relation(index, &relation_args)? + }; self.apply( "conv_call", &[("p", p), ("env", env)], @@ -484,7 +491,7 @@ impl<'a> Writer<'_, 'a> { _ => return Err("the lowering of && is not a cond".to_owned()), }; let else_term = SemanticTerm::Bool { value: false }; - self.conditional_of(Selection::And, left, right, &else_term, (c, t, e), scope) + self.conditional_of(Selection::And, None, left, right, &else_term, (c, t, e), scope) } SemanticTerm::Or { left, right } => { let (c, t, e) = match expr { @@ -496,7 +503,7 @@ impl<'a> Writer<'_, 'a> { _ => return Err("the lowering of || is not a cond".to_owned()), }; let then_term = SemanticTerm::Bool { value: true }; - self.conditional_of(Selection::Or, left, &then_term, right, (c, t, e), scope) + self.conditional_of(Selection::Or, None, left, &then_term, right, (c, t, e), scope) } SemanticTerm::Not { value } => { let operand_exprs = match expr { @@ -847,13 +854,26 @@ impl<'a> Writer<'_, 'a> { scope: &Scope, ) -> Result { let result_ty = self.infer(whole, scope)?; - self.conditional_of(Selection::If(result_ty), condition, then_value, else_value, exprs, scope) + let node = self.node_of(scope, whole); + self.conditional_of( + Selection::If(result_ty), + node, + condition, + then_value, + else_value, + exprs, + scope, + ) } - /// A conditional whose value is selected as `kind` says. + /// A conditional whose value is selected as `kind` says. A numbered + /// `if` of a well-founded body (`node`) is written by cases on its + /// condition with the equation each arm was taken by. + #[allow(clippy::too_many_arguments)] fn conditional_of( &mut self, kind: Selection, + node: Option, condition: &SemanticTerm, then_value: &SemanticTerm, else_value: &SemanticTerm, @@ -862,14 +882,39 @@ impl<'a> Writer<'_, 'a> { ) -> Result { let (p, env) = (self.prog, scope.env); let tested = self.prove(condition, exprs.0, scope)?; - let then_pf = self.prove(then_value, exprs.1, scope)?; - let else_pf = self.prove(else_value, exprs.2, scope)?; + let equations = node.map(|_| (self.b.free(), self.b.free())); + let (then_scope, else_scope) = match (node, equations) { + (Some(node), Some((on_true, on_false))) => ( + Self::entering(scope, Step::If { node, polarity: true }, on_true, &[]), + Self::entering(scope, Step::If { node, polarity: false }, on_false, &[]), + ), + _ => (scope.clone(), scope.clone()), + }; + let then_pf = self.prove(then_value, exprs.1, &then_scope)?; + let else_pf = self.prove(else_value, exprs.2, &else_scope)?; let condition_src = self.src(condition, scope)?; let bool = self.b.konst("Bool"); let guard = self.b.free(); let then_fits = self.fits_of(then_pf)?; let else_fits = self.fits_of(else_pf)?; - let fits_body = self.ite(bool, guard, then_fits, else_fits); + let fits_body = match equations { + None => self.ite(bool, guard, then_fits, else_fits), + Some((on_true, on_false)) => { + let (tru, fls) = (self.tru(), self.fls()); + let true_eq = self.eq(bool, guard, tru); + let false_eq = self.eq(bool, guard, fls); + let then_arm = self.arm_lambda(&[], (on_true, true_eq), then_fits); + let else_arm = self.arm_lambda(&[], (on_false, false_eq), else_fits); + let one = CoreLevel::Succ(Box::new(CoreLevel::Zero)); + self.equation_cases( + &SemanticType::Bool, + guard, + bool, + one, + vec![(0, else_arm), (1, then_arm)], + )? + } + }; let predicate = self.b.lam_over("__b", EXPLICIT, bool, guard, fits_body); let encoded = match &kind { Selection::If(result_ty) => { @@ -877,7 +922,25 @@ impl<'a> Writer<'_, 'a> { let encoder = self.enc(result_ty)?; let then_src = self.src(then_value, scope)?; let else_src = self.src(else_value, scope)?; - let selected = self.ite(lean, guard, then_src, else_src); + let selected = match equations { + None => self.ite(lean, guard, then_src, else_src), + Some(_) => { + let (tru, fls) = (self.tru(), self.fls()); + let true_eq = self.eq(bool, guard, tru); + let false_eq = self.eq(bool, guard, fls); + let (unused_true, unused_false) = (self.b.free(), self.b.free()); + let then_arm = self.arm_lambda(&[], (unused_true, true_eq), then_src); + let else_arm = self.arm_lambda(&[], (unused_false, false_eq), else_src); + let one = CoreLevel::Succ(Box::new(CoreLevel::Zero)); + self.equation_cases( + &SemanticType::Bool, + guard, + lean, + one, + vec![(0, else_arm), (1, then_arm)], + )? + } + }; self.b.app(encoder, selected) } Selection::And => { @@ -897,8 +960,8 @@ impl<'a> Writer<'_, 'a> { let value_fn = self.b.lam_over("__b", EXPLICIT, bool, guard, encoded); let tru = self.tru(); let fls = self.fls(); - let then_branch = self.guarded(condition_src, tru, then_pf)?; - let else_branch = self.guarded(condition_src, fls, else_pf)?; + let then_branch = self.guarded(condition_src, tru, then_pf, equations.map(|pair| pair.0))?; + let else_branch = self.guarded(condition_src, fls, else_pf, equations.map(|pair| pair.1))?; self.apply( "conv_cond", &[("p", p), ("env", env)], @@ -913,15 +976,24 @@ impl<'a> Writer<'_, 'a> { ) } - /// `fun (h : cond = value) => proof`, with its type. - fn guarded(&mut self, condition: Term, value: Term, pf: Pf) -> Result<(Term, Term), String> { + /// `fun (h : cond = value) => proof`, with its type; `hypothesis` is the + /// free variable the proof reads the equation by, when it does. + fn guarded( + &mut self, + condition: Term, + value: Term, + pf: Pf, + hypothesis: Option, + ) -> Result<(Term, Term), String> { let bool = self.b.konst("Bool"); let ty = self.eq(bool, condition, value); - let hypothesis = self.b.free(); + let hypothesis = match hypothesis { + Some(hypothesis) => hypothesis, + None => self.b.free(), + }; let proof = self.b.lam_over("_", EXPLICIT, ty, hypothesis, pf.proof); - let lifted = self.b.lift(pf.ty, 1, 0); - let pi = self.b.pi("_", EXPLICIT, ty, lifted); - Ok((proof, pi)) + let result = self.b.pi_over("_", EXPLICIT, ty, hypothesis, pf.ty); + Ok((proof, result)) } // --- Let ---------------------------------------------------------------- diff --git a/crates/lexlean/src/production/certificate/data_a/rows.rs b/crates/lexlean/src/production/certificate/data_a/rows.rs index 2accc35e7..000b79197 100644 --- a/crates/lexlean/src/production/certificate/data_a/rows.rs +++ b/crates/lexlean/src/production/certificate/data_a/rows.rs @@ -376,15 +376,32 @@ impl<'a> Writer<'_, 'a> { ), terms: set.terms.clone(), env, + path: Vec::new(), }; let program = self.gen.program; let body_expr = &program.functions[*index as usize].body; let pf = self.prove(body, body_expr, &scope)?; self.function_rows(*index, &sig, pf, env)?; } - Recursion::Structural { .. } | Recursion::WellFounded { .. } => { + Recursion::WellFounded { termination, group } => { + self.wf_definition( + *index, + &module, + &name, + &type_arguments, + &locals, + &result, + body, + &termination, + &group, + site, + type_parameters, + recursive, + )?; + } + Recursion::Structural { .. } => { return Err(format!( - "`{module}.{name}` is recursive, which the data certificate does not yet write" + "`{module}.{name}` is structurally recursive outside a structural component" )); } } @@ -423,6 +440,7 @@ impl<'a> Writer<'_, 'a> { ), terms: Vec::new(), env, + path: Vec::new(), }; let program = self.gen.program; let body_expr = &program.functions[*index as usize].body; @@ -452,6 +470,7 @@ impl<'a> Writer<'_, 'a> { ctx, terms: set.terms.clone(), env, + path: Vec::new(), }; let denotation = self.src(body, &scope)?; let sig = self.signature(*index, &clocals, &set, &result, denotation)?; @@ -467,7 +486,7 @@ impl<'a> Writer<'_, 'a> { } /// The denotation of a definition instance applied to its locals. - fn denotation( + pub(super) fn denotation( &mut self, module: &str, name: &str, diff --git a/crates/lexlean/src/production/certificate/data_a/src.rs b/crates/lexlean/src/production/certificate/data_a/src.rs index ac2d0837e..5516f36df 100644 --- a/crates/lexlean/src/production/certificate/data_a/src.rs +++ b/crates/lexlean/src/production/certificate/data_a/src.rs @@ -65,7 +65,7 @@ impl Writer<'_, '_> { )) } - fn member_const(&mut self, member: &MemberRef, site: &Site) -> Result { + pub(super) fn member_const(&mut self, member: &MemberRef, site: &Site) -> Result { let name = self.member_name(member, site)?; Ok(self.b.konst(&name)) } @@ -740,7 +740,25 @@ impl Writer<'_, '_> { let condition = self.src(condition, scope)?; let then_value = self.src(then_value, scope)?; let else_value = self.src(else_value, scope)?; - self.ite(ty, condition, then_value, else_value) + if self.node_of(scope, term).is_some() { + let bool = self.b.konst("Bool"); + let (tru, fls) = (self.tru(), self.fls()); + let (on_true, on_false) = (self.b.free(), self.b.free()); + let true_eq = self.eq(bool, condition, tru); + let false_eq = self.eq(bool, condition, fls); + let then_arm = self.arm_lambda(&[], (on_true, true_eq), then_value); + let else_arm = self.arm_lambda(&[], (on_false, false_eq), else_value); + let one = CoreLevel::Succ(Box::new(CoreLevel::Zero)); + self.equation_cases( + &SemanticType::Bool, + condition, + ty, + one, + vec![(0, else_arm), (1, then_arm)], + )? + } else { + self.ite(ty, condition, then_value, else_value) + } } SemanticTerm::Match { scrutinee, @@ -996,6 +1014,8 @@ impl Writer<'_, '_> { let result_ty = self.infer(term, scope)?; let result = self.lean_ty(&result_ty)?; let discriminant = self.src(scrutinee, scope)?; + let numbered = self.node_of(scope, term).is_some(); + let carrier = self.lean_ty(&scrutinee_ty)?; let mut alternatives = Vec::new(); for branch in branches { let locals = self.gen.branch_locals(branch, scrutinee, &scope.ctx)?; @@ -1007,18 +1027,40 @@ impl Writer<'_, '_> { } let inner = self.bind(scope, locals.clone(), &frees, scope.env); let mut body = self.src(&branch.body, &inner)?; - for (position, (local, _)) in locals.iter().enumerate().rev() { - body = self.b.lam_over( - &super::super::identifier(&local.name), - super::EXPLICIT, - types[position], - frees[position], - body, - ); - } let (constructor, _) = self.gen.source.branch(branch, &scrutinee_ty, &scope.ctx.site)?; + if numbered { + let value = self.constructor_value(&constructor, &scrutinee_ty, branch, &frees, scope)?; + let equation = self.eq(carrier, discriminant, value); + let hypothesis = self.b.free(); + let binders: Vec<(String, Term, Term)> = locals + .iter() + .enumerate() + .map(|(position, (local, _))| { + ( + super::super::identifier(&local.name), + types[position], + frees[position], + ) + }) + .collect(); + body = self.arm_lambda(&binders, (hypothesis, equation), body); + } else { + for (position, (local, _)) in locals.iter().enumerate().rev() { + body = self.b.lam_over( + &super::super::identifier(&local.name), + super::EXPLICIT, + types[position], + frees[position], + body, + ); + } + } alternatives.push((self.constructor_position(&constructor)?, body)); } + if numbered { + let one = CoreLevel::Succ(Box::new(CoreLevel::Zero)); + return self.equation_cases(&scrutinee_ty, discriminant, result, one, alternatives); + } alternatives.sort_by_key(|(position, _)| *position); let minors: Vec = alternatives.into_iter().map(|(_, body)| body).collect(); self.cases_on_const(&scrutinee_ty, discriminant, result, &minors) diff --git a/crates/lexlean/src/production/certificate/data_a/structural.rs b/crates/lexlean/src/production/certificate/data_a/structural.rs index ab41d4f6e..cd2ed9c68 100644 --- a/crates/lexlean/src/production/certificate/data_a/structural.rs +++ b/crates/lexlean/src/production/certificate/data_a/structural.rs @@ -337,7 +337,7 @@ impl<'a> Writer<'_, 'a> { /// The types of a position's functions as one: the pair type /// `PProd a (PProd b c)`, or for statements `a ∧ b ∧ c`. - fn pack_types(&mut self, tys: &[Term], relation: bool) -> Term { + pub(super) fn pack_types(&mut self, tys: &[Term], relation: bool) -> Term { let Some((first, rest)) = tys.split_first() else { return self.b.konst("PUnit"); }; @@ -356,7 +356,7 @@ impl<'a> Writer<'_, 'a> { } /// The pair of values of types `tys`. - fn pack_values(&mut self, tys: &[Term], values: &[Term], relation: bool) -> Term { + pub(super) fn pack_values(&mut self, tys: &[Term], values: &[Term], relation: bool) -> Term { let (first, rest) = (values[0], &values[1..]); if rest.is_empty() { return first; @@ -374,7 +374,7 @@ impl<'a> Writer<'_, 'a> { } /// The component `index` of a packed value of types `tys`. - fn unpack(&mut self, tys: &[Term], index: usize, packed: Term, relation: bool) -> Term { + pub(super) fn unpack(&mut self, tys: &[Term], index: usize, packed: Term, relation: bool) -> Term { if tys.len() == 1 { return packed; } @@ -689,6 +689,7 @@ impl<'a> Writer<'_, 'a> { ), terms, env, + path: Vec::new(), }; let top = &program.functions[member.index as usize].body; let (match_ty, scrutinee_expr, arms) = match top { @@ -924,7 +925,7 @@ impl<'a> Writer<'_, 'a> { /// The component `index` of a packed value of types `tys`, built on the /// builder alone. -fn unpack_with( +pub(super) fn unpack_with( b: &mut crate::production::kernel::Builder, tys: &[Term], index: usize, @@ -957,7 +958,7 @@ fn unpack_with( } /// The pair type of `tys`, built on the builder alone. -fn pack_types_with(b: &mut crate::production::kernel::Builder, tys: &[Term], relation: bool) -> Term { +pub(super) fn pack_types_with(b: &mut crate::production::kernel::Builder, tys: &[Term], relation: bool) -> Term { let Some((first, rest)) = tys.split_first() else { return b.konst("PUnit"); }; @@ -974,3 +975,26 @@ fn pack_types_with(b: &mut crate::production::kernel::Builder, tys: &[Term], rel b.apps(head, &[*first, packed]) } } + +/// The pair of values of types `tys`, built on the builder alone. +pub(super) fn pack_values_with( + b: &mut crate::production::kernel::Builder, + tys: &[Term], + values: &[Term], + relation: bool, +) -> Term { + let (first, rest) = (values[0], &values[1..]); + if rest.is_empty() { + return first; + } + let packed_ty = pack_types_with(b, &tys[1..], relation); + let packed = pack_values_with(b, &tys[1..], rest, relation); + if relation { + let intro = b.konst("And.intro"); + b.apps(intro, &[tys[0], packed_ty, first, packed]) + } else { + let one = CoreLevel::Succ(Box::new(CoreLevel::Zero)); + let head = b.konst_at("PProd.mk", vec![one.clone(), one]); + b.apps(head, &[tys[0], packed_ty, first, packed]) + } +} diff --git a/crates/lexlean/src/production/certificate/data_a/wf.rs b/crates/lexlean/src/production/certificate/data_a/wf.rs new file mode 100644 index 000000000..a3b9d55d7 --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/wf.rs @@ -0,0 +1,528 @@ +//! Well-founded recursion (SPEC.md §17.12, §17.17.4): a definition whose +//! recursive calls the source justifies with a measure and decrease evidence. +//! +//! Its width predicate and relation theorem are each `WellFounded.fix` over +//! the measure `InvImage`-ordered on the packed parameters, a recursive call +//! being the induction hypothesis applied to the packed arguments and the +//! source's evidence that they decrease the measure. The evidence quantifies +//! the arms enclosing the call, so every `if` and `match` of the body is +//! written by cases on its scrutinee with the equation the arm was taken by +//! (the elaborator's `match h : e with`), and the call reads that equation +//! from the free variable that stands for it. +//! +//! A recursive call is first written as a marker applied to its arguments and +//! its evidence, and the marker becomes what each use of the derivation needs +//! when the rows are built: the induction hypothesis in the width predicate +//! and the relation theorem, the predicate itself where a type states it, +//! and the implementation record's direct recursion. + +use super::structural::pack_values_with; +use super::{PathStep, Pf, RowKind, Scope, WfSite, WfState, Writer, EXPLICIT}; +use crate::ir::core::CoreLevel; +use crate::ir::semantic::{SemanticParameter, SemanticTerm, SemanticType, Step, SemanticTermination}; +use crate::production::certificate::{CLocal, Ctx, Gen, Hypotheses}; +use crate::production::kernel::{Builder, Term}; +use crate::production::source::{Local, Site}; +use std::collections::BTreeSet; + +fn one() -> CoreLevel { + CoreLevel::Succ(Box::new(CoreLevel::Zero)) +} + +/// Whether two steps are the same arm of the same node. +fn same_step(a: Step, b: Step) -> bool { + match (a, b) { + ( + Step::If { + node: left, + polarity: first, + }, + Step::If { + node: right, + polarity: second, + }, + ) => left == right && first == second, + ( + Step::Match { + node: left, + branch: first, + }, + Step::Match { + node: right, + branch: second, + }, + ) => left == right && first == second, + (Step::If { node: _, polarity: _ }, Step::Match { node: _, branch: _ }) + | (Step::Match { node: _, branch: _ }, Step::If { node: _, polarity: _ }) => false, + } +} + +/// The hypotheses a body's recursive calls are justified under. +impl<'a> Writer<'_, 'a> { + /// The number of the `if` or `match` `term` is, when it is a node of the + /// well-founded body under derivation. + pub(super) fn node_of(&self, scope: &Scope, term: &SemanticTerm) -> Option { + scope + .ctx + .hypotheses + .get(&(std::ptr::from_ref(term) as usize)) + .copied() + } + + /// `scope` inside the arm `step`, taken by the equation `hypothesis`. + pub(super) fn entering( + scope: &Scope, + step: Step, + hypothesis: Term, + fields: &[Term], + ) -> Scope { + let mut out = scope.clone(); + out.path.push(PathStep { + step, + hypothesis, + fields: fields.to_vec(), + }); + out + } + + /// `fun fields (hypothesis : ty) => body`: a minor premise of a cases + /// that carries the equation its arm was taken by. + pub(super) fn arm_lambda( + &mut self, + binders: &[(String, Term, Term)], + hypothesis: (Term, Term), + body: Term, + ) -> Term { + let (free, ty) = hypothesis; + let mut out = self.b.lam_over("__h", EXPLICIT, ty, free, body); + for (name, ty, free) in binders.iter().rev() { + out = self.b.lam_over(name, EXPLICIT, *ty, *free, out); + } + out + } + + /// `match h : scrutinee with …`: the eliminator of the scrutinee's type + /// at the motive `fun a => scrutinee = a → result`, applied to the + /// minors (by constructor position, from [`Self::arm_lambda`]) and to + /// `rfl`. + pub(super) fn equation_cases( + &mut self, + ty: &SemanticType, + scrutinee: Term, + result: Term, + level: CoreLevel, + mut minors: Vec<(usize, Term)>, + ) -> Result { + minors.sort_by_key(|(position, _)| *position); + let minors: Vec = minors.into_iter().map(|(_, term)| term).collect(); + let carrier = self.lean_ty(ty)?; + let bound = self.b.free(); + let equation = self.eq(carrier, scrutinee, bound); + let arrow = self.b.arrow(equation, result); + let motive = self.b.lam_over("__a", EXPLICIT, carrier, bound, arrow); + let cases = self.cases_on(ty, scrutinee, motive, level, &minors)?; + let refl = self.b.konst1("Eq.refl"); + let proof = self.b.apps(refl, &[carrier, scrutinee]); + Ok(self.b.app(cases, proof)) + } + + /// The relation of a recursive call of the well-founded definition under + /// derivation: a marker for the theorem at its arguments and the + /// evidence that they decrease the measure, whose statement writes the + /// width predicate as the matching marker. + pub(super) fn wf_call( + &mut self, + index: u64, + arguments: &[SemanticTerm], + relation_args: &[Term], + scope: &Scope, + ) -> Result { + let state = self + .wf + .clone() + .ok_or("a recursive call outside a well-founded definition")?; + let path: Vec = scope.path.iter().map(|step| step.step).collect(); + let site = state + .sites + .iter() + .find(|site| { + site.arguments.as_slice() == arguments + && site.path.len() == path.len() + && site + .path + .iter() + .zip(&path) + .all(|(left, right)| same_step(*left, *right)) + }) + .ok_or("a recursive call with no decrease obligation in the plan")?; + let lemma = self.member_const(&site.evidence, &scope.ctx.site)?; + let mut evidence_args = state.type_arguments.clone(); + evidence_args.extend(state.parameters.iter().copied()); + for step in &scope.path { + evidence_args.extend(step.fields.iter().copied()); + } + for step in &scope.path { + evidence_args.push(step.hypothesis); + } + let evidence = self.b.apps(lemma, &evidence_args); + let plain = self.call_relation(index, relation_args)?; + let fits_name = format!("{}.__fits_{index}", self.name); + let call_name = format!("{}.__wfcall_{index}", self.name); + let ty = self + .b + .rewrite_apps(plain.ty, &mut |b: &mut Builder, name: &str, args: &[Term]| { + if name != fits_name { + return None; + } + let head = b.konst(&call_name); + let mut all = args.to_vec(); + all.push(evidence); + Some(b.apps(head, &all)) + }); + let proof_head = self.own(&format!("__wfrel_{index}")); + let mut all = relation_args.to_vec(); + all.push(evidence); + let proof = self.b.apps(proof_head, &all); + Ok(Pf { proof, ty }) + } + + /// `FunRel __prog index arguments (Rel fits value)`. + fn statement_of(&mut self, index: u64, arguments: Term, fits: Term, value: Term) -> Term { + let fun_rel = self.lp("FunRel"); + let prog = self.prog; + let i = self.nat_u(index); + let observation = self.rel(fits, value); + self.b.apps(fun_rel, &[prog, i, arguments, observation]) + } + + /// The rows of one well-founded definition: its width predicate and its + /// relation theorem by `WellFounded.fix`, and the implementation record + /// of the predicate by direct recursion. + #[allow(clippy::too_many_arguments, clippy::too_many_lines)] + pub(super) fn wf_definition( + &mut self, + index: u64, + module: &str, + name: &str, + type_arguments: &[SemanticType], + locals: &[(Local, SemanticType)], + result: &SemanticType, + body: &'a SemanticTerm, + termination: &SemanticTermination, + group: &BTreeSet, + site: Site, + type_parameters: Vec<(String, SemanticType)>, + recursive: bool, + ) -> Result<(), String> { + if group.len() > 1 { + return Err(format!( + "`{module}.{name}` is one of a mutual well-founded group" + )); + } + if !recursive { + return Err(format!( + "`{module}.{name}` names a termination measure and does not recurse" + )); + } + let clocals: Vec = Gen::scope(locals, ""); + let declared: Vec = type_parameters.iter().map(|(n, _)| n.clone()).collect(); + let parameters: Vec = locals + .iter() + .map(|(local, _)| SemanticParameter { + name: local.name.clone(), + r#type: local.ty.clone(), + }) + .collect(); + let plan = + crate::ir::semantic::well_founded_plan(name, group, &declared, ¶meters, body)?; + if plan.sites.len() != termination.evidence.len() { + return Err(format!( + "`{module}.{name}` has {} recursive calls and {} decrease theorems", + plan.sites.len(), + termination.evidence.len() + )); + } + let hypotheses: Hypotheses = plan + .addresses + .iter() + .enumerate() + .map(|(index, address)| (*address, index)) + .collect(); + let sites: Vec = plan + .sites + .iter() + .zip(&termination.evidence) + .map(|(site, evidence)| WfSite { + path: site.path.clone(), + arguments: site.arguments.clone(), + evidence: evidence.clone(), + }) + .collect(); + let set = self.local_set(index, &clocals)?; + if set.terms.iter().any(|terms| terms.companion.is_some()) { + return Err(format!( + "`{module}.{name}` is well-founded with a function parameter" + )); + } + let denotation = self.denotation(module, name, type_arguments, &set)?; + let sig = self.signature(index, &clocals, &set, result, denotation)?; + self.sigs.insert(index, sig.clone()); + let env = self.entry_env(index, &clocals, &set.terms)?; + let scope = Scope { + ctx: Ctx::new(site, type_parameters, clocals, hypotheses, index), + terms: set.terms.clone(), + env, + path: Vec::new(), + }; + let mut lean_arguments = Vec::new(); + for argument in type_arguments { + lean_arguments.push(self.lean_ty(argument)?); + } + let state = WfState { + index, + sites, + type_arguments: lean_arguments, + parameters: set.terms.iter().map(|terms| terms.value).collect(), + }; + self.wf = Some(state.clone()); + let program = self.gen.program; + let body_expr = &program.functions[index as usize].body; + let proved = self.prove(body, body_expr, &scope); + self.wf = None; + let pf = proved?; + let measure = self.src(&termination.measure, &scope)?; + self.wf_rows(index, &sig, pf, env, &state, measure, result, denotation) + } + + /// The rows themselves. + #[allow(clippy::too_many_arguments, clippy::too_many_lines)] + fn wf_rows( + &mut self, + index: u64, + sig: &super::rows::Sig, + body: Pf, + env: Term, + state: &WfState, + measure: Term, + result: &SemanticType, + denotation: Term, + ) -> Result<(), String> { + let params: Vec = sig.fits_binders.iter().map(|binder| binder.free).collect(); + let tys: Vec = sig.fits_binders.iter().map(|binder| binder.ty).collect(); + if params.is_empty() { + return Err("a well-founded definition with no parameters".to_owned()); + } + let bool = self.b.konst("Bool"); + let domain = self.pack_types(&tys, false); + let x = self.b.free(); + let projections: Vec = (0..tys.len()) + .map(|position| self.unpack(&tys, position, x, false)) + .collect(); + // The relation the induction hypothesis is over: the measure's order. + let measured = self.b.substitute(measure, ¶ms, &projections); + let measure_fn = self.b.lam_over("__x", EXPLICIT, domain, x, measured); + let measure_head = self.b.konst_at("measure", vec![one()]); + let order = self.b.apps(measure_head, &[domain, measure_fn]); + let rel_head = self.b.konst_at("WellFoundedRelation.rel", vec![one()]); + let rel = self.b.apps(rel_head, &[domain, order]); + let wf_head = self.b.konst_at("WellFoundedRelation.wf", vec![one()]); + let well_founded = self.b.apps(wf_head, &[domain, order]); + let packed = self.pack_values(&tys, ¶ms, false); + let call_name = format!("{}.__wfcall_{index}", self.name); + let rel_name = format!("{}.__wfrel_{index}", self.name); + let fits_name = format!("{}.__fits_{index}", self.name); + let unsafe_name = format!("{}.__fits_{index}._unsafe_rec", self.name); + let fits_raw = self.fits_of(body)?; + // The width predicate: the body with the induction hypothesis for + // each recursive call. + let ih = self.b.free(); + let fits_with_ih = { + let (call_name, tys) = (call_name.clone(), tys.clone()); + self.b + .rewrite_apps(fits_raw, &mut |b: &mut Builder, name: &str, args: &[Term]| { + if name != call_name { + return None; + } + let (evidence, rest) = args.split_last()?; + let packed = pack_values_with(b, &tys, rest, false); + Some(b.apps(ih, &[packed, *evidence])) + }) + }; + let fits_body = self.b.substitute(fits_with_ih, ¶ms, &projections); + let y = self.b.free(); + let below = self.b.apps(rel, &[y, x]); + let ih_body = self.b.arrow(below, bool); + let ih_ty = self.b.pi_over("y", EXPLICIT, domain, y, ih_body); + let step_fn = self.b.lam_over("ih", EXPLICIT, ih_ty, ih, fits_body); + let fits_step = self.b.lam_over("x", EXPLICIT, domain, x, step_fn); + let fits_motive = self.b.lam("_", EXPLICIT, domain, bool); + let fix = self.b.konst_at("WellFounded.fix", vec![one(), one()]); + let fix_at = |b: &mut Builder, at: Term| { + b.apps(fix, &[domain, fits_motive, rel, well_founded, fits_step, at]) + }; + let fits_fix = fix_at(&mut self.b, packed); + let fits_value = self.lambdas(&sig.fits_binders, fits_fix); + let fits_ty = self.pis(&sig.fits_binders, bool); + self.push_row( + &format!("__fits_{index}"), + RowKind::Definition { compiled: true }, + fits_ty, + fits_value, + )?; + // The relation theorem. Its body derives the relation of the + // definition's own body; the equations `fix_eq` and the definition's + // `eq_def` carry it to the statement. + let (body_fits, body_value) = self.rel_parts(body.ty)?; + let (head, parts) = self.b.spine(sig.statement); + let observation = self.rel(body_fits, body_value); + let mut inner_sig = sig.clone(); + inner_sig.statement = self + .b + .apps(head, &[parts[0], parts[1], parts[2], observation]); + let inner = self.relation_proof(index, &inner_sig, env, body)?; + let fits_var = self.b.free(); + let moved_fits_statement = self.statement_of(index, sig.arguments, fits_var, body_value); + let fits_motive_eq = self + .b + .lam_over("__f", EXPLICIT, bool, fits_var, moved_fits_statement); + let again = self.b.free(); + let below_again = self.b.apps(rel, &[again, packed]); + let recur_again = fix_at(&mut self.b, again); + let lifted = self.b.lift(recur_again, 1, 0); + let inside = self.b.lam("_", EXPLICIT, below_again, lifted); + let hypothesis_fn = self.b.lam_over("y", EXPLICIT, domain, again, inside); + let unfolded = self.b.apps(fits_step, &[packed, hypothesis_fn]); + let fix_eq = self.b.konst_at("WellFounded.fix_eq", vec![one(), one()]); + let equation = self.b.apps( + fix_eq, + &[domain, fits_motive, rel, well_founded, fits_step, packed], + ); + let symm = self.b.konst1("Eq.symm"); + let flipped = self.b.apps(symm, &[bool, fits_fix, unfolded, equation]); + let ndrec = self.b.konst_at("Eq.ndrec", vec![CoreLevel::Zero, one()]); + let by_fits = self.b.apps( + ndrec, + &[ + bool, + body_fits, + fits_motive_eq, + inner.proof, + sig.fits_applied, + flipped, + ], + ); + // The denotation's equation. + let encoder = self.enc(result)?; + let value_node = self.b.node(body_value).clone(); + let denoted = match value_node { + crate::ir::core::CoreNode::App { f, x: denoted } if f == encoder => denoted, + _ => return Err("the value of a well-founded body is not an encoding".to_owned()), + }; + let lean_result = self.lean_ty(result)?; + let value_var = self.b.free(); + let encoded_var = self.b.app(encoder, value_var); + let moved_value_statement = + self.statement_of(index, sig.arguments, sig.fits_applied, encoded_var); + let value_motive = self + .b + .lam_over("__d", EXPLICIT, lean_result, value_var, moved_value_statement); + let (definition, definition_args) = self.b.spine(denotation); + let definition_name = match self.b.node(definition).clone() { + crate::ir::core::CoreNode::Const { n, .. } => n, + _ => return Err("the denotation of a definition is not a constant".to_owned()), + }; + let eq_def_head = self.b.konst(&format!("{definition_name}.eq_def")); + let eq_def = self.b.apps(eq_def_head, &definition_args); + let flipped_def = self + .b + .apps(symm, &[lean_result, denotation, denoted, eq_def]); + let by_value = self.b.apps( + ndrec, + &[ + lean_result, + denoted, + value_motive, + by_fits, + denotation, + flipped_def, + ], + ); + // The markers: the predicate where a type states it, the induction + // hypothesis where a derivation uses the theorem. + let ihr = self.b.free(); + let transported = { + let tys = tys.clone(); + let fits_name = fits_name.clone(); + let (call_name, rel_name) = (call_name.clone(), rel_name.clone()); + self.b + .rewrite_apps(by_value, &mut |b: &mut Builder, name: &str, args: &[Term]| { + if name == call_name { + let (_, rest) = args.split_last()?; + let head = b.konst(&fits_name); + return Some(b.apps(head, rest)); + } + if name == rel_name { + let (evidence, rest) = args.split_last()?; + let packed = pack_values_with(b, &tys, rest, false); + return Some(b.apps(ihr, &[packed, *evidence])); + } + None + }) + }; + let rel_body = self.b.substitute(transported, ¶ms, &projections); + let statement_at = |writer: &mut Self, at: Term| -> Vec { + (0..tys.len()) + .map(|position| writer.unpack(&tys, position, at, false)) + .collect() + }; + let below_projections = statement_at(self, y); + let below_statement = self + .b + .substitute(sig.statement, ¶ms, &below_projections); + let ihr_body = self.b.arrow(below, below_statement); + let ihr_ty = self.b.pi_over("y", EXPLICIT, domain, y, ihr_body); + let rel_step_fn = self.b.lam_over("ih", EXPLICIT, ihr_ty, ihr, rel_body); + let rel_step = self.b.lam_over("x", EXPLICIT, domain, x, rel_step_fn); + let motive_var = self.b.free(); + let motive_projections = statement_at(self, motive_var); + let motive_body = self + .b + .substitute(sig.statement, ¶ms, &motive_projections); + let rel_motive = self + .b + .lam_over("__x", EXPLICIT, domain, motive_var, motive_body); + let rel_fix = self.b.konst_at("WellFounded.fix", vec![one(), CoreLevel::Zero]); + let rel_fixed = self.b.apps( + rel_fix, + &[domain, rel_motive, rel, well_founded, rel_step, packed], + ); + let rel_value = self.lambdas(&sig.rel_binders, rel_fixed); + let rel_ty = self.pis(&sig.rel_binders, sig.statement); + self.push_row( + &format!("__rel_{index}"), + RowKind::Theorem, + rel_ty, + rel_value, + )?; + // The implementation record of the predicate: direct recursion. + let direct = { + let (call_name, unsafe_name) = (call_name, unsafe_name); + self.b + .rewrite_apps(fits_raw, &mut |b: &mut Builder, name: &str, args: &[Term]| { + if name != call_name { + return None; + } + let (_, rest) = args.split_last()?; + let head = b.konst(&unsafe_name); + Some(b.apps(head, rest)) + }) + }; + let unsafe_value = self.lambdas(&sig.fits_binders, direct); + let unsafe_ty = self.pis(&sig.fits_binders, bool); + let short = format!("__fits_{index}._unsafe_rec"); + self.push_row(&short, RowKind::Partial, unsafe_ty, unsafe_value)?; + self.group_rows(&[short]); + let _ = state; + Ok(()) + } +} From 1509a625cf39f231bd60340e790d38d95b173575 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 17:30:22 +0000 Subject: [PATCH 09/41] #89 stage 3: certificate A data: violation matches by cases on the pair and each Boolean Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- .../src/production/certificate/data_a.rs | 6 +- .../production/certificate/data_a/calls.rs | 14 +- .../production/certificate/data_a/encoders.rs | 26 +- .../production/certificate/data_a/matching.rs | 346 ++++++++++++++++-- .../production/certificate/data_a/prove.rs | 83 ++++- .../production/certificate/data_a/recplan.rs | 6 +- .../src/production/certificate/data_a/rows.rs | 43 ++- .../src/production/certificate/data_a/src.rs | 26 +- .../certificate/data_a/structural.rs | 65 +++- .../src/production/certificate/data_a/wf.rs | 79 ++-- crates/lexlean/src/production/kernel/ext.rs | 29 +- 11 files changed, 570 insertions(+), 153 deletions(-) diff --git a/crates/lexlean/src/production/certificate/data_a.rs b/crates/lexlean/src/production/certificate/data_a.rs index 64aafb7d7..543ef822b 100644 --- a/crates/lexlean/src/production/certificate/data_a.rs +++ b/crates/lexlean/src/production/certificate/data_a.rs @@ -34,8 +34,8 @@ use crate::ir::semantic::{MemberRef, SemanticTerm, SemanticType, Step}; mod boundary; mod calls; -mod lean; mod encoders; +mod lean; mod matching; mod prims; mod prove; @@ -55,7 +55,9 @@ pub(super) enum RowKind { Theorem, /// A definition; `compiled` is whether the code generator runs over it, /// which the differential evaluator needs of `denote` and what it calls. - Definition { compiled: bool }, + Definition { + compiled: bool, + }, /// The compiler's implementation record of a recursive definition: the /// same function written with direct recursion, for the code generator, /// which has no code for a recursor. The members of one mutual group diff --git a/crates/lexlean/src/production/certificate/data_a/calls.rs b/crates/lexlean/src/production/certificate/data_a/calls.rs index d60d3520f..49d7a9714 100644 --- a/crates/lexlean/src/production/certificate/data_a/calls.rs +++ b/crates/lexlean/src/production/certificate/data_a/calls.rs @@ -133,9 +133,13 @@ impl<'a> Writer<'_, 'a> { } let mut out = body(self, &frees)?; for position in (0..frees.len()).rev() { - out = self - .b - .lam_over(&format!("__p{position}"), EXPLICIT, types[position], frees[position], out); + out = self.b.lam_over( + &format!("__p{position}"), + EXPLICIT, + types[position], + frees[position], + out, + ); } Ok(out) } @@ -288,9 +292,7 @@ impl<'a> Writer<'_, 'a> { .iter() .map(|argument| self.gen.source.close(argument, &scope.ctx.site)) .collect(); - let index = self - .gen - .instance_index(&module, &function.name, &closed)?; + let index = self.gen.instance_index(&module, &function.name, &closed)?; let list = self.operands(env, &[])?; let fi = self.nat_u(index); let proof = self.apply( diff --git a/crates/lexlean/src/production/certificate/data_a/encoders.rs b/crates/lexlean/src/production/certificate/data_a/encoders.rs index 784622dbb..f051694ae 100644 --- a/crates/lexlean/src/production/certificate/data_a/encoders.rs +++ b/crates/lexlean/src/production/certificate/data_a/encoders.rs @@ -2,9 +2,9 @@ //! `__enc_k` for each type, and for a recursive group of types the auxiliary //! encoders of the containers nested in them with their bundles. -use super::recplan::{PositionKind, RecPlan}; -use super::super::data::AuxKind; use super::super::components; +use super::super::data::AuxKind; +use super::recplan::{PositionKind, RecPlan}; use super::{RowKind, Writer, EXPLICIT}; use crate::ir::core::CoreLevel; use crate::ir::semantic::SemanticType; @@ -260,12 +260,13 @@ impl<'a> Writer<'_, 'a> { } } } - let body = - self.constructor_encoding(&plan, position, ctor, &frees, &hypotheses)?; + let body = self.constructor_encoding(&plan, position, ctor, &frees, &hypotheses)?; let mut minor = body; for (index, hypothesis) in hypotheses.iter().enumerate().rev() { if let (Some(free), Some(ty)) = (hypothesis, hypothesis_types[index]) { - minor = self.b.lam_over(&format!("__ih{index}"), EXPLICIT, ty, *free, minor); + minor = + self.b + .lam_over(&format!("__ih{index}"), EXPLICIT, ty, *free, minor); } } for (index, free) in frees.iter().enumerate().rev() { @@ -294,14 +295,11 @@ impl<'a> Writer<'_, 'a> { args.extend(minors.iter().copied()); args.push(free); let applied = self.b.apps(recursor, &args); - let value = self.b.lam_over("__v", EXPLICIT, domains[position], free, applied); + let value = self + .b + .lam_over("__v", EXPLICIT, domains[position], free, applied); let row_ty = self.b.arrow(domains[position], results[position]); - self.push_row( - &name, - RowKind::Definition { compiled: true }, - row_ty, - value, - )?; + self.push_row(&name, RowKind::Definition { compiled: true }, row_ty, value)?; names.push(name); } self.group_rows(&names); @@ -349,7 +347,9 @@ impl<'a> Writer<'_, 'a> { results[position], &cases_minors, )?; - let value = self.b.lam_over("__v", EXPLICIT, domains[position], free, cases); + let value = self + .b + .lam_over("__v", EXPLICIT, domains[position], free, cases); let row_ty = self.b.arrow(domains[position], results[position]); self.push_row(&unsafe_names[position], RowKind::Partial, row_ty, value)?; } diff --git a/crates/lexlean/src/production/certificate/data_a/matching.rs b/crates/lexlean/src/production/certificate/data_a/matching.rs index be92daf05..546b063e7 100644 --- a/crates/lexlean/src/production/certificate/data_a/matching.rs +++ b/crates/lexlean/src/production/certificate/data_a/matching.rs @@ -45,11 +45,7 @@ impl<'a> Writer<'_, 'a> { ("fields", list), ("env'", extended), ], - &[ - Given::Refl, - Given::Refl, - Given::Proof(body.proof, body.ty), - ], + &[Given::Refl, Given::Refl, Given::Proof(body.proof, body.ty)], ) } @@ -218,7 +214,8 @@ impl<'a> Writer<'_, 'a> { let value = self.constructor_value(&constructor, &scrutinee_ty, branch, &frees, scope)?; let encoded = self.b.app(scrutinee_enc, value); - let mut proof = self.arm_hit(env, encoded, arms, position, &fields, arm_env, body_pf)?; + let mut proof = + self.arm_hit(env, encoded, arms, position, &fields, arm_env, body_pf)?; for earlier in (0..position).rev() { proof = self.arm_miss(env, encoded, arms, earlier, proof)?; } @@ -238,13 +235,15 @@ impl<'a> Writer<'_, 'a> { // The arm is taken by an equation the decrease evidence of a // recursive call within it reads: each of the predicate, the // value, and the derivation carries it as a hypothesis. - let lean_value = self.constructor_value(&constructor, &scrutinee_ty, branch, &frees, scope)?; + let lean_value = + self.constructor_value(&constructor, &scrutinee_ty, branch, &frees, scope)?; let of_bound = self.eq(lean_scrutinee, bound, lean_value); let of_scrutinee = self.eq(lean_scrutinee, scrutinee_src, lean_value); let unused = self.b.free(); let fits_arm = self.arm_lambda(&binders, (equation_free, of_bound), fits); let value_arm = self.arm_lambda(&binders, (unused, of_bound), body_src); - let proof_arm = self.arm_lambda(&binders, (equation_free, of_scrutinee), proof.proof); + let proof_arm = + self.arm_lambda(&binders, (equation_free, of_scrutinee), proof.proof); fits_minors.push((ctor_position, fits_arm)); value_minors.push((ctor_position, value_arm)); proof_minors.push((ctor_position, proof_arm)); @@ -275,11 +274,19 @@ impl<'a> Writer<'_, 'a> { // arms' values by cases on a. let one = CoreLevel::Succ(Box::new(CoreLevel::Zero)); let g_body = if node.is_some() { - self.equation_cases(&scrutinee_ty, bound, bool, one.clone(), sorted_with(&fits_minors))? + self.equation_cases( + &scrutinee_ty, + bound, + bool, + one.clone(), + sorted_with(&fits_minors), + )? } else { self.cases_on_const(&scrutinee_ty, bound, bool, &fits_terms)? }; - let g = self.b.lam_over("__a", EXPLICIT, lean_scrutinee, bound, g_body); + let g = self + .b + .lam_over("__a", EXPLICIT, lean_scrutinee, bound, g_body); let w_inner = if node.is_some() { self.equation_cases( &scrutinee_ty, @@ -292,7 +299,9 @@ impl<'a> Writer<'_, 'a> { self.cases_on_const(&scrutinee_ty, bound, result_lean, &value_terms)? }; let w_body = self.b.app(result_enc, w_inner); - let w = self.b.lam_over("__a", EXPLICIT, lean_scrutinee, bound, w_body); + let w = self + .b + .lam_over("__a", EXPLICIT, lean_scrutinee, bound, w_body); // ha : ∀ a, sv = a → ConvA p env (enc a) arms (Rel (G a) (W a)), // proved by cases on a with the arms' derivations as minors. let arms_term = self.arms_list(arms)?; @@ -301,32 +310,23 @@ impl<'a> Writer<'_, 'a> { let w_a = self.b.app(w, bound); let rel = self.rel(g_a, w_a); let conv_a = self.lp("ConvA"); - let target = self.b.apps(conv_a, &[p, env, encoded_bound, arms_term, rel]); + let target = self + .b + .apps(conv_a, &[p, env, encoded_bound, arms_term, rel]); let equation = self.eq(lean_scrutinee, scrutinee_src, bound); let cases = if node.is_some() { let dependent = self.b.arrow(equation, target); let motive = self .b .lam_over("__a", EXPLICIT, lean_scrutinee, bound, dependent); - let cases = self.cases_on( - &scrutinee_ty, - bound, - motive, - CoreLevel::Zero, - &proof_terms, - )?; + let cases = + self.cases_on(&scrutinee_ty, bound, motive, CoreLevel::Zero, &proof_terms)?; self.b.app(cases, hypothesis) } else { let motive = self .b .lam_over("__a", EXPLICIT, lean_scrutinee, bound, target); - self.cases_on( - &scrutinee_ty, - bound, - motive, - CoreLevel::Zero, - &proof_terms, - )? + self.cases_on(&scrutinee_ty, bound, motive, CoreLevel::Zero, &proof_terms)? }; let with_hypothesis = self .b @@ -358,8 +358,10 @@ impl<'a> Writer<'_, 'a> { } /// The proof of a match over a violation: its pair of Booleans, then each - /// Boolean in turn (`lower::violation_match`). - #[allow(clippy::too_many_arguments)] + /// Boolean in turn (`lower::violation_match`). The derivation is by cases + /// on the pair and on each Boolean, so that each of the four + /// constructors is a closed run of the program's arms. + #[allow(clippy::too_many_arguments, clippy::too_many_lines)] fn violation_matching( &mut self, result_ty: &SemanticType, @@ -370,7 +372,291 @@ impl<'a> Writer<'_, 'a> { scrutinee_expr: &'a Expr, scope: &Scope, ) -> Result { - let _ = (result_ty, match_ty, scrutinee, branches, arms, scrutinee_expr, scope); - Err("violation matches".to_owned()) + let (p, env) = (self.prog, scope.env); + let scrutinee_ty = self.infer(scrutinee, scope)?; + let pair_arm = arms.first().ok_or("a violation match lowered to no arms")?; + let (first_var, second_var) = match pair_arm.binders.as_slice() { + [first, second] => (*first, *second), + _ => return Err("a violation match binds other than its pair".to_owned()), + }; + let (halves_ty, first_expr, halves) = match &pair_arm.body { + Expr::Match { + ty, + scrutinee, + arms, + } => (ty, scrutinee.as_ref(), arms.as_slice()), + _ => return Err("a violation match's pair arm is not a match".to_owned()), + }; + let _ = first_expr; + let bool = self.b.konst("Bool"); + let value_ty = self.value_ty(); + let result_lean = self.lean_ty(result_ty)?; + let result_enc = self.enc(result_ty)?; + let scrutinee_enc = self.enc(&scrutinee_ty)?; + let lean_scrutinee = self.lean_ty(&scrutinee_ty)?; + let tested = self.prove(scrutinee, scrutinee_expr, scope)?; + let scrutinee_src = self.src(scrutinee, scope)?; + // The body of each of the four constructors. + let mut leaves: Vec<((bool, bool), &SemanticBranch)> = Vec::new(); + for branch in branches { + let (constructor, _) = + self.gen + .source + .branch(branch, &scrutinee_ty, &scope.ctx.site)?; + match constructor { + crate::production::source::Constructor::Violation(first, second) => { + leaves.push(((first, second), branch)); + } + crate::production::source::Constructor::Bool(_) + | crate::production::source::Constructor::Zero + | crate::production::source::Constructor::Succ + | crate::production::source::Constructor::Nil + | crate::production::source::Constructor::Cons + | crate::production::source::Constructor::OptionNone + | crate::production::source::Constructor::OptionSome + | crate::production::source::Constructor::Ok + | crate::production::source::Constructor::Error + | crate::production::source::Constructor::Document { ty: _, index: _ } => { + return Err("a violation match over another constructor".to_owned()); + } + } + } + let arms_term = self.arms_list(arms)?; + let halves_term = self.arms_list(halves)?; + // Per (first, second): the derivation of the program's arms, the + // arm's predicate and its value. + let mut proofs: Vec<[Term; 2]> = vec![[Term::default(); 2]; 2]; + let mut fits_leaves: Vec<[Term; 2]> = vec![[Term::default(); 2]; 2]; + let mut value_leaves: Vec<[Term; 2]> = vec![[Term::default(); 2]; 2]; + let first_ty = self.ty_term(halves_ty); + for (first_value, second_value) in + [(false, false), (false, true), (true, false), (true, true)] + { + let branch = leaves + .iter() + .find(|(key, _)| *key == (first_value, second_value)) + .map(|(_, branch)| *branch) + .ok_or("a violation match misses a constructor")?; + let first_bool = self.boolean(first_value); + let second_bool = self.boolean(second_value); + let bool_enc = self.ts("Value.bool"); + let first_encoded = self.b.app(bool_enc, first_bool); + let second_encoded = self.b.app(bool_enc, second_bool); + let pair_env = self.extend_env( + env, + &[first_var, second_var], + &[first_encoded, second_encoded], + )?; + let inner_scope = Scope { + env: pair_env, + ..scope.clone() + }; + let second_arms = &halves + .get(usize::from(first_value)) + .ok_or("a violation match lowered to fewer halves")?; + let (second_ty, leaf_arms) = match &second_arms.body { + Expr::Match { ty, arms, .. } => (ty, arms.as_slice()), + _ => return Err("a violation half is not a match".to_owned()), + }; + let leaf = leaf_arms + .get(usize::from(second_value)) + .ok_or("a violation half lowered to fewer arms")?; + let body_pf = self.prove(&branch.body, &leaf.body, &inner_scope)?; + let fits = self.fits_of(body_pf)?; + let value = self.src(&branch.body, &inner_scope)?; + // The innermost Boolean, then the first, then the pair. + let leaf_hit = self.arm_hit( + pair_env, + second_encoded, + leaf_arms, + usize::from(second_value), + &[], + pair_env, + body_pf, + )?; + let mut leaf_chain = leaf_hit; + for earlier in (0..usize::from(second_value)).rev() { + leaf_chain = + self.arm_miss(pair_env, second_encoded, leaf_arms, earlier, leaf_chain)?; + } + let second_var_term = self.nat_u(second_var); + let second_test = self.apply( + "conv_var", + &[ + ("p", p), + ("env", pair_env), + ("x", second_var_term), + ("v", second_encoded), + ], + &[Given::Refl], + )?; + let second_arms_term = self.arms_list(leaf_arms)?; + let second_match_ty = self.ty_term(second_ty); + let inner = self.apply( + "conv_match", + &[ + ("p", p), + ("env", pair_env), + ("t", second_match_ty), + ("arms", second_arms_term), + ], + &[ + Given::Proof(second_test.proof, second_test.ty), + Given::Proof(leaf_chain.proof, leaf_chain.ty), + ], + )?; + let half_hit = self.arm_hit( + pair_env, + first_encoded, + halves, + usize::from(first_value), + &[], + pair_env, + inner, + )?; + let mut half_chain = half_hit; + for earlier in (0..usize::from(first_value)).rev() { + half_chain = self.arm_miss(pair_env, first_encoded, halves, earlier, half_chain)?; + } + let first_var_term = self.nat_u(first_var); + let first_test = self.apply( + "conv_var", + &[ + ("p", p), + ("env", pair_env), + ("x", first_var_term), + ("v", first_encoded), + ], + &[Given::Refl], + )?; + let outer = self.apply( + "conv_match", + &[ + ("p", p), + ("env", pair_env), + ("t", first_ty), + ("arms", halves_term), + ], + &[ + Given::Proof(first_test.proof, first_test.ty), + Given::Proof(half_chain.proof, half_chain.ty), + ], + )?; + let pair_value = self.b.pair(bool, bool, first_bool, second_bool); + let encoded = self.b.app(scrutinee_enc, pair_value); + let top = self.arm_hit( + env, + encoded, + arms, + 0, + &[first_encoded, second_encoded], + pair_env, + outer, + )?; + proofs[usize::from(first_value)][usize::from(second_value)] = top.proof; + fits_leaves[usize::from(first_value)][usize::from(second_value)] = fits; + value_leaves[usize::from(first_value)][usize::from(second_value)] = value; + } + let _ = (value_ty, match_ty); + // G a and W a: the leaves by cases on the pair, then on each Boolean. + let bound = self.b.free(); + let first = self.b.free(); + let second = self.b.free(); + let by_cases = + |writer: &mut Self, leaves: &[[Term; 2]], result: Term| -> Result { + let mut rows = Vec::new(); + for row in leaves { + rows.push(writer.cases_on_const(&SemanticType::Bool, second, result, row)?); + } + let by_first = writer.cases_on_const(&SemanticType::Bool, first, result, &rows)?; + let inner = writer + .b + .lam_over("__second", EXPLICIT, bool, second, by_first); + let minor = writer.b.lam_over("__first", EXPLICIT, bool, first, inner); + writer.cases_on_const(&scrutinee_ty, bound, result, &[minor]) + }; + let g_body = by_cases(self, &fits_leaves, bool)?; + let g = self + .b + .lam_over("__a", EXPLICIT, lean_scrutinee, bound, g_body); + let w_inner = by_cases(self, &value_leaves, result_lean)?; + let w_body = self.b.app(result_enc, w_inner); + let w = self + .b + .lam_over("__a", EXPLICIT, lean_scrutinee, bound, w_body); + // ha: by cases on the pair, then on each Boolean, the closed runs. + let conv_a = self.lp("ConvA"); + let target_at = |writer: &mut Self, value: Term| -> Term { + let encoded = writer.b.app(scrutinee_enc, value); + let g_at = writer.b.app(g, value); + let w_at = writer.b.app(w, value); + let rel = writer.rel(g_at, w_at); + writer.b.apps(conv_a, &[p, env, encoded, arms_term, rel]) + }; + let zero = CoreLevel::Zero; + let mut rows = Vec::new(); + for first_value in [false, true] { + let first_bool = self.boolean(first_value); + let free_second = self.b.free(); + let pair_at = self.b.pair(bool, bool, first_bool, free_second); + let target = target_at(self, pair_at); + let motive = self + .b + .lam_over("__second", EXPLICIT, bool, free_second, target); + let minors: Vec = proofs[usize::from(first_value)].to_vec(); + rows.push(self.cases_on(&SemanticType::Bool, second, motive, zero.clone(), &minors)?); + } + let pair_at = self.b.pair(bool, bool, first, second); + let first_target = target_at(self, pair_at); + let first_motive = self + .b + .lam_over("__first", EXPLICIT, bool, first, first_target); + let by_first = self.cases_on( + &SemanticType::Bool, + first, + first_motive, + zero.clone(), + &rows, + )?; + let minor = { + let inner = self + .b + .lam_over("__second", EXPLICIT, bool, second, by_first); + self.b.lam_over("__first", EXPLICIT, bool, first, inner) + }; + let target = target_at(self, bound); + let motive = self + .b + .lam_over("__a", EXPLICIT, lean_scrutinee, bound, target); + let cases = self.cases_on(&scrutinee_ty, bound, motive, zero, &[minor])?; + let hypothesis = self.b.free(); + let equation = self.eq(lean_scrutinee, scrutinee_src, bound); + let with_hypothesis = self + .b + .lam_over("__h", EXPLICIT, equation, hypothesis, cases); + let ha = self + .b + .lam_over("__a", EXPLICIT, lean_scrutinee, bound, with_hypothesis); + let scrutinee_expr_term = self.expr_term(scrutinee_expr)?; + let ty = self.ty_term(match_ty); + self.apply( + "conv_matchV", + &[ + ("p", p), + ("env", env), + ("α", lean_scrutinee), + ("t", ty), + ("s", scrutinee_expr_term), + ("arms", arms_term), + ], + &[ + Given::Term(scrutinee_enc), + Given::Term(g), + Given::Term(w), + Given::Term(scrutinee_src), + Given::Proof(tested.proof, tested.ty), + Given::Term(ha), + ], + ) } } diff --git a/crates/lexlean/src/production/certificate/data_a/prove.rs b/crates/lexlean/src/production/certificate/data_a/prove.rs index 30a72bc48..81f8289d9 100644 --- a/crates/lexlean/src/production/certificate/data_a/prove.rs +++ b/crates/lexlean/src/production/certificate/data_a/prove.rs @@ -60,7 +60,10 @@ impl<'a> Writer<'_, 'a> { return Ok((fits, value)); } let (_, args) = self.b.spine(ty); - let observation = args.get(3).copied().ok_or("a derivation type with no relation")?; + let observation = args + .get(3) + .copied() + .ok_or("a derivation type with no relation")?; let (_, parts) = self.b.spine(observation); match parts.as_slice() { [fits, value] => Ok((*fits, *value)), @@ -452,7 +455,11 @@ impl<'a> Writer<'_, 'a> { condition, then_branch, else_branch, - } => (condition.as_ref(), then_branch.as_ref(), else_branch.as_ref()), + } => ( + condition.as_ref(), + then_branch.as_ref(), + else_branch.as_ref(), + ), _ => return Err("the lowering of a conditional is not a cond".to_owned()), }; self.conditional(term, condition, then_value, else_value, (c, t, e), scope) @@ -487,11 +494,23 @@ impl<'a> Writer<'_, 'a> { condition, then_branch, else_branch, - } => (condition.as_ref(), then_branch.as_ref(), else_branch.as_ref()), + } => ( + condition.as_ref(), + then_branch.as_ref(), + else_branch.as_ref(), + ), _ => return Err("the lowering of && is not a cond".to_owned()), }; let else_term = SemanticTerm::Bool { value: false }; - self.conditional_of(Selection::And, None, left, right, &else_term, (c, t, e), scope) + self.conditional_of( + Selection::And, + None, + left, + right, + &else_term, + (c, t, e), + scope, + ) } SemanticTerm::Or { left, right } => { let (c, t, e) = match expr { @@ -499,11 +518,23 @@ impl<'a> Writer<'_, 'a> { condition, then_branch, else_branch, - } => (condition.as_ref(), then_branch.as_ref(), else_branch.as_ref()), + } => ( + condition.as_ref(), + then_branch.as_ref(), + else_branch.as_ref(), + ), _ => return Err("the lowering of || is not a cond".to_owned()), }; let then_term = SemanticTerm::Bool { value: true }; - self.conditional_of(Selection::Or, None, left, &then_term, right, (c, t, e), scope) + self.conditional_of( + Selection::Or, + None, + left, + &then_term, + right, + (c, t, e), + scope, + ) } SemanticTerm::Not { value } => { let operand_exprs = match expr { @@ -540,7 +571,9 @@ impl<'a> Writer<'_, 'a> { SemanticTerm::First { value } | SemanticTerm::Second { value } => { let inner_expr = match expr { Expr::First { value } | Expr::Second { value } => value.as_ref(), - _ => return Err("the lowering of a projection is not first or second".to_owned()), + _ => { + return Err("the lowering of a projection is not first or second".to_owned()) + } }; let ty = self.infer(value, scope)?; let (left, right) = match &ty { @@ -718,7 +751,9 @@ impl<'a> Writer<'_, 'a> { operands, .. } if operands.len() == 2 => Ok((&operands[0], &operands[1])), - _ => Err("a literal collection lowered to something other than a cons chain".to_owned()), + _ => { + Err("a literal collection lowered to something other than a cons chain".to_owned()) + } } } @@ -773,7 +808,10 @@ impl<'a> Writer<'_, 'a> { let value = self.value_ty(); let map = self.b.konst_at( "List.map", - vec![crate::ir::core::CoreLevel::Zero, crate::ir::core::CoreLevel::Zero], + vec![ + crate::ir::core::CoreLevel::Zero, + crate::ir::core::CoreLevel::Zero, + ], ); let t = self.b.apps(map, &[alpha, value, e, tail_src]); self.apply("construct_cons", &[("h", h), ("t", t)], &[]) @@ -885,8 +923,24 @@ impl<'a> Writer<'_, 'a> { let equations = node.map(|_| (self.b.free(), self.b.free())); let (then_scope, else_scope) = match (node, equations) { (Some(node), Some((on_true, on_false))) => ( - Self::entering(scope, Step::If { node, polarity: true }, on_true, &[]), - Self::entering(scope, Step::If { node, polarity: false }, on_false, &[]), + Self::entering( + scope, + Step::If { + node, + polarity: true, + }, + on_true, + &[], + ), + Self::entering( + scope, + Step::If { + node, + polarity: false, + }, + on_false, + &[], + ), ), _ => (scope.clone(), scope.clone()), }; @@ -960,8 +1014,10 @@ impl<'a> Writer<'_, 'a> { let value_fn = self.b.lam_over("__b", EXPLICIT, bool, guard, encoded); let tru = self.tru(); let fls = self.fls(); - let then_branch = self.guarded(condition_src, tru, then_pf, equations.map(|pair| pair.0))?; - let else_branch = self.guarded(condition_src, fls, else_pf, equations.map(|pair| pair.1))?; + let then_branch = + self.guarded(condition_src, tru, then_pf, equations.map(|pair| pair.0))?; + let else_branch = + self.guarded(condition_src, fls, else_pf, equations.map(|pair| pair.1))?; self.apply( "conv_cond", &[("p", p), ("env", env)], @@ -1045,5 +1101,4 @@ impl<'a> Writer<'_, 'a> { let ty = self.b.substitute(applied.ty, &[free], &[bound_src]); Ok(Pf { proof, ty }) } - } diff --git a/crates/lexlean/src/production/certificate/data_a/recplan.rs b/crates/lexlean/src/production/certificate/data_a/recplan.rs index ce41c7be5..1b3245db7 100644 --- a/crates/lexlean/src/production/certificate/data_a/recplan.rs +++ b/crates/lexlean/src/production/certificate/data_a/recplan.rs @@ -286,7 +286,11 @@ impl Writer<'_, '_> { match &entry.kind { PositionKind::Block { index: _ } => { let shape = self.gen.source.document(&entry.ty)?; - Ok(shape.fields.iter().map(|fields| ctor(fields.clone())).collect()) + Ok(shape + .fields + .iter() + .map(|fields| ctor(fields.clone())) + .collect()) } PositionKind::Builtin => Ok(entry.constructors.clone()), PositionKind::Aux(aux) => { diff --git a/crates/lexlean/src/production/certificate/data_a/rows.rs b/crates/lexlean/src/production/certificate/data_a/rows.rs index 000b79197..7ba35da14 100644 --- a/crates/lexlean/src/production/certificate/data_a/rows.rs +++ b/crates/lexlean/src/production/certificate/data_a/rows.rs @@ -3,9 +3,7 @@ use std::collections::BTreeMap; -use super::super::{ - function_type, identifier, CLocal, Ctx, Hypotheses, Recursion, Subject, -}; +use super::super::{function_type, identifier, CLocal, Ctx, Hypotheses, Recursion, Subject}; use super::{CompTerms, LocalTerm, Pf, RowKind, Scope, Writer, EXPLICIT}; use crate::calculus::Expr; use crate::ir::semantic::{MemberRef, SemanticParameter, SemanticTerm, SemanticType}; @@ -272,7 +270,10 @@ impl<'a> Writer<'_, 'a> { .ok_or_else(|| format!("the program has no function {index}"))?; let function = self.function_term(function)?; let (_, parts) = self.b.spine(sig.statement); - let observation = parts.get(3).copied().ok_or("a statement with no observation")?; + let observation = parts + .get(3) + .copied() + .ok_or("a statement with no observation")?; let p = self.prog; let fi = self.nat_u(index); self.apply( @@ -285,11 +286,7 @@ impl<'a> Writer<'_, 'a> { ("fn", function), ("env", env), ], - &[ - Given::Refl, - Given::Refl, - Given::Proof(body.proof, body.ty), - ], + &[Given::Refl, Given::Refl, Given::Proof(body.proof, body.ty)], ) } @@ -314,7 +311,12 @@ impl<'a> Writer<'_, 'a> { let proof = self.relation_proof(index, sig, env, body)?; let rel_value = self.lambdas(&sig.rel_binders, proof.proof); let rel_ty = self.pis(&sig.rel_binders, sig.statement); - self.push_row(&format!("__rel_{index}"), RowKind::Theorem, rel_ty, rel_value) + self.push_row( + &format!("__rel_{index}"), + RowKind::Theorem, + rel_ty, + rel_value, + ) } /// One strongly connected component of the relation graph. @@ -362,8 +364,10 @@ impl<'a> Writer<'_, 'a> { )); } let set = self.local_set(*index, &clocals)?; - let denotation = self.denotation(&module, &name, &type_arguments, &set)?; - let sig = self.signature(*index, &clocals, &set, &result, denotation)?; + let denotation = + self.denotation(&module, &name, &type_arguments, &set)?; + let sig = + self.signature(*index, &clocals, &set, &result, denotation)?; self.sigs.insert(*index, sig.clone()); let env = self.entry_env(*index, &clocals, &set.terms)?; let scope = Scope { @@ -461,10 +465,7 @@ impl<'a> Writer<'_, 'a> { let ctx = self.gen.lambda_ctx(*index)?; let clocals = ctx.locals.clone(); let set = self.local_set(*index, &clocals)?; - let result = self - .gen - .source - .infer(body, &ctx.sources(), &ctx.site)?; + let result = self.gen.source.infer(body, &ctx.sources(), &ctx.site)?; let env = self.entry_env(*index, &clocals, &set.terms)?; let scope = Scope { ctx, @@ -561,7 +562,9 @@ impl<'a> Writer<'_, 'a> { crate::ir::core::CoreLevel::Zero, ))], ); - let observation = self.b.apps(cond, &[obs, sig.fits_applied, valued, overflow]); + let observation = self + .b + .apps(cond, &[obs, sig.fits_applied, valued, overflow]); let denote_value = self.lambdas(&sig.fits_binders, observation); let denote_ty = self.pis(&sig.fits_binders, obs); self.push_row( @@ -576,11 +579,7 @@ impl<'a> Writer<'_, 'a> { let entry = self.nat_u(0); let rel = self.rel(sig.fits_applied, sig.value); let root_ty = self.b.apps(run_conv, &[prog, entry, sig.arguments, rel]); - let root = self.apply( - "run_of_funRel", - &[], - &[Given::Proof(rel0.proof, rel0.ty)], - )?; + let root = self.apply("run_of_funRel", &[], &[Given::Proof(rel0.proof, rel0.ty)])?; let root_value = self.lambdas(&sig.rel_binders, root.proof); let root_pi = self.pis(&sig.rel_binders, root_ty); self.push_row("root", RowKind::Theorem, root_pi, root_value)?; diff --git a/crates/lexlean/src/production/certificate/data_a/src.rs b/crates/lexlean/src/production/certificate/data_a/src.rs index 5516f36df..8b303d8ea 100644 --- a/crates/lexlean/src/production/certificate/data_a/src.rs +++ b/crates/lexlean/src/production/certificate/data_a/src.rs @@ -137,8 +137,7 @@ impl Writer<'_, '_> { let name = Self::fixed_name(ty)?; let to = self.runtime(site, "LexLeanRuntime", "Fixed.toToMathInt")?; let fixed = self.lean_ty(ty)?; - let instance = - self.runtime(site, "LexLeanRuntime", &format!("instFixed{name}"))?; + let instance = self.runtime(site, "LexLeanRuntime", &format!("instFixed{name}"))?; Ok(self.b.apps(to, &[fixed, instance])) } } @@ -287,9 +286,7 @@ impl Writer<'_, '_> { }; leading.push(self.runtime(site, "LexLeanRuntime", instance)?); } - SemanticPrimitive::Append - | SemanticPrimitive::Length - | SemanticPrimitive::Slice => { + SemanticPrimitive::Append | SemanticPrimitive::Length | SemanticPrimitive::Slice => { let ty = first.ok_or("a sequence operation without an operand")?; leading.push(self.lean_ty(ty)?); let class = match operation { @@ -548,7 +545,10 @@ impl Writer<'_, '_> { for argument in arguments { params.push(self.lean_ty(argument)?); } - (self.b.konst_at(&format!("{owner}.casesOn"), vec![level]), params) + ( + self.b.konst_at(&format!("{owner}.casesOn"), vec![level]), + params, + ) } SemanticType::Product { left, right } => { let left = self.lean_ty(left)?; @@ -616,7 +616,11 @@ impl Writer<'_, '_> { // --- The denotation --------------------------------------------------- - pub(super) fn src_all(&mut self, terms: &[SemanticTerm], scope: &Scope) -> Result, String> { + pub(super) fn src_all( + &mut self, + terms: &[SemanticTerm], + scope: &Scope, + ) -> Result, String> { terms.iter().map(|term| self.src(term, scope)).collect() } @@ -1027,9 +1031,13 @@ impl Writer<'_, '_> { } let inner = self.bind(scope, locals.clone(), &frees, scope.env); let mut body = self.src(&branch.body, &inner)?; - let (constructor, _) = self.gen.source.branch(branch, &scrutinee_ty, &scope.ctx.site)?; + let (constructor, _) = + self.gen + .source + .branch(branch, &scrutinee_ty, &scope.ctx.site)?; if numbered { - let value = self.constructor_value(&constructor, &scrutinee_ty, branch, &frees, scope)?; + let value = + self.constructor_value(&constructor, &scrutinee_ty, branch, &frees, scope)?; let equation = self.eq(carrier, discriminant, value); let hypothesis = self.b.free(); let binders: Vec<(String, Term, Term)> = locals diff --git a/crates/lexlean/src/production/certificate/data_a/structural.rs b/crates/lexlean/src/production/certificate/data_a/structural.rs index cd2ed9c68..9e2255693 100644 --- a/crates/lexlean/src/production/certificate/data_a/structural.rs +++ b/crates/lexlean/src/production/certificate/data_a/structural.rs @@ -5,6 +5,7 @@ use std::collections::{BTreeMap, HashMap}; +use super::super::{CLocal, Ctx, Gen, Hypotheses, Recursion, Subject}; use super::recplan::RecPlan; use super::rows::{Binder, LocalSet, Sig}; use super::{LocalTerm, Pf, RowKind, Scope, Writer, EXPLICIT}; @@ -13,7 +14,6 @@ use crate::ir::core::CoreLevel; use crate::ir::semantic::{SemanticTerm, SemanticType}; use crate::production::kernel::{Given, Term}; use crate::production::source::{Local, Site}; -use super::super::{CLocal, Ctx, Gen, Hypotheses, Recursion, Subject}; /// One function of a structurally recursive group. struct Member<'a> { @@ -71,7 +71,11 @@ impl<'a> Writer<'_, 'a> { else { return Err(format!("function {index} is not a definition")); }; - let Recursion::Structural { argument, mutual: _ } = recursion else { + let Recursion::Structural { + argument, + mutual: _, + } = recursion + else { return Err(format!("`{module}.{name}` is not structurally recursive")); }; let clocals = Gen::scope(&locals, ""); @@ -94,7 +98,10 @@ impl<'a> Writer<'_, 'a> { 1 } }; - let fits_arg = clocals[..arg_position].iter().map(|l| width(l, false)).sum(); + let fits_arg = clocals[..arg_position] + .iter() + .map(|l| width(l, false)) + .sum(); let rel_arg = clocals[..arg_position].iter().map(|l| width(l, true)).sum(); members.push(Member { index: *index, @@ -374,7 +381,13 @@ impl<'a> Writer<'_, 'a> { } /// The component `index` of a packed value of types `tys`. - pub(super) fn unpack(&mut self, tys: &[Term], index: usize, packed: Term, relation: bool) -> Term { + pub(super) fn unpack( + &mut self, + tys: &[Term], + index: usize, + packed: Term, + relation: bool, + ) -> Term { if tys.len() == 1 { return packed; } @@ -610,9 +623,7 @@ impl<'a> Writer<'_, 'a> { let mut chosen = None; for (branch_index, branch) in branches.iter().enumerate() { let (constructor, fields) = - self.gen - .source - .branch(branch, &argument_ty, &member.site)?; + self.gen.source.branch(branch, &argument_ty, &member.site)?; if self.constructor_position(&constructor)? == ctor { chosen = Some((branch_index, branch, constructor, fields)); break; @@ -745,7 +756,12 @@ impl<'a> Writer<'_, 'a> { let match_ty_term = self.ty_term(match_ty); let matched = self.apply( "conv_match", - &[("p", p), ("env", env), ("t", match_ty_term), ("arms", arms_term)], + &[ + ("p", p), + ("env", env), + ("t", match_ty_term), + ("arms", arms_term), + ], &[ Given::Proof(tested.proof, tested.ty), Given::Proof(chain.proof, chain.ty), @@ -765,8 +781,14 @@ impl<'a> Writer<'_, 'a> { .b .substitute(member.sig.statement, &member.sig.rel_params, &to); let (_, parts) = self.b.spine(statement); - let observation = parts.get(3).copied().ok_or("a statement with no observation")?; - let arguments = parts.get(2).copied().ok_or("a statement with no arguments")?; + let observation = parts + .get(3) + .copied() + .ok_or("a statement with no observation")?; + let arguments = parts + .get(2) + .copied() + .ok_or("a statement with no arguments")?; let function = self.function_term(&program.functions[member.index as usize])?; let fi = self.nat_u(member.index); let funrel = self.apply( @@ -854,7 +876,11 @@ impl<'a> Writer<'_, 'a> { if hypothesis.position != call_position { return None; } - let packed = if relation { hypothesis.rel } else { hypothesis.fits }; + let packed = if relation { + hypothesis.rel + } else { + hypothesis.fits + }; let (tys, frees) = tys_cache.get(&(call_position, relation))?; let at: Vec = tys .iter() @@ -914,13 +940,16 @@ impl<'a> Writer<'_, 'a> { let direct_body = self.b.substitute(direct_body, &from_fits, &to_fits); let mut direct = direct_body; for (index, free) in field_frees.iter().enumerate().rev() { - direct = self - .b - .lam_over(&format!("__x{index}"), EXPLICIT, field_types[index], *free, direct); + direct = self.b.lam_over( + &format!("__x{index}"), + EXPLICIT, + field_types[index], + *free, + direct, + ); } Ok((fits_inner, rel_inner, direct, constructed)) } - } /// The component `index` of a packed value of types `tys`, built on the @@ -958,7 +987,11 @@ pub(super) fn unpack_with( } /// The pair type of `tys`, built on the builder alone. -pub(super) fn pack_types_with(b: &mut crate::production::kernel::Builder, tys: &[Term], relation: bool) -> Term { +pub(super) fn pack_types_with( + b: &mut crate::production::kernel::Builder, + tys: &[Term], + relation: bool, +) -> Term { let Some((first, rest)) = tys.split_first() else { return b.konst("PUnit"); }; diff --git a/crates/lexlean/src/production/certificate/data_a/wf.rs b/crates/lexlean/src/production/certificate/data_a/wf.rs index a3b9d55d7..ec2165958 100644 --- a/crates/lexlean/src/production/certificate/data_a/wf.rs +++ b/crates/lexlean/src/production/certificate/data_a/wf.rs @@ -19,7 +19,9 @@ use super::structural::pack_values_with; use super::{PathStep, Pf, RowKind, Scope, WfSite, WfState, Writer, EXPLICIT}; use crate::ir::core::CoreLevel; -use crate::ir::semantic::{SemanticParameter, SemanticTerm, SemanticType, Step, SemanticTermination}; +use crate::ir::semantic::{ + SemanticParameter, SemanticTerm, SemanticTermination, SemanticType, Step, +}; use crate::production::certificate::{CLocal, Ctx, Gen, Hypotheses}; use crate::production::kernel::{Builder, Term}; use crate::production::source::{Local, Site}; @@ -52,8 +54,20 @@ fn same_step(a: Step, b: Step) -> bool { branch: second, }, ) => left == right && first == second, - (Step::If { node: _, polarity: _ }, Step::Match { node: _, branch: _ }) - | (Step::Match { node: _, branch: _ }, Step::If { node: _, polarity: _ }) => false, + ( + Step::If { + node: _, + polarity: _, + }, + Step::Match { node: _, branch: _ }, + ) + | ( + Step::Match { node: _, branch: _ }, + Step::If { + node: _, + polarity: _, + }, + ) => false, } } @@ -70,12 +84,7 @@ impl<'a> Writer<'_, 'a> { } /// `scope` inside the arm `step`, taken by the equation `hypothesis`. - pub(super) fn entering( - scope: &Scope, - step: Step, - hypothesis: Term, - fields: &[Term], - ) -> Scope { + pub(super) fn entering(scope: &Scope, step: Step, hypothesis: Term, fields: &[Term]) -> Scope { let mut out = scope.clone(); out.path.push(PathStep { step, @@ -168,9 +177,9 @@ impl<'a> Writer<'_, 'a> { let plain = self.call_relation(index, relation_args)?; let fits_name = format!("{}.__fits_{index}", self.name); let call_name = format!("{}.__wfcall_{index}", self.name); - let ty = self - .b - .rewrite_apps(plain.ty, &mut |b: &mut Builder, name: &str, args: &[Term]| { + let ty = self.b.rewrite_apps( + plain.ty, + &mut |b: &mut Builder, name: &str, args: &[Term]| { if name != fits_name { return None; } @@ -178,7 +187,8 @@ impl<'a> Writer<'_, 'a> { let mut all = args.to_vec(); all.push(evidence); Some(b.apps(head, &all)) - }); + }, + ); let proof_head = self.own(&format!("__wfrel_{index}")); let mut all = relation_args.to_vec(); all.push(evidence); @@ -338,15 +348,17 @@ impl<'a> Writer<'_, 'a> { let ih = self.b.free(); let fits_with_ih = { let (call_name, tys) = (call_name.clone(), tys.clone()); - self.b - .rewrite_apps(fits_raw, &mut |b: &mut Builder, name: &str, args: &[Term]| { + self.b.rewrite_apps( + fits_raw, + &mut |b: &mut Builder, name: &str, args: &[Term]| { if name != call_name { return None; } let (evidence, rest) = args.split_last()?; let packed = pack_values_with(b, &tys, rest, false); Some(b.apps(ih, &[packed, *evidence])) - }) + }, + ) }; let fits_body = self.b.substitute(fits_with_ih, ¶ms, &projections); let y = self.b.free(); @@ -358,7 +370,10 @@ impl<'a> Writer<'_, 'a> { let fits_motive = self.b.lam("_", EXPLICIT, domain, bool); let fix = self.b.konst_at("WellFounded.fix", vec![one(), one()]); let fix_at = |b: &mut Builder, at: Term| { - b.apps(fix, &[domain, fits_motive, rel, well_founded, fits_step, at]) + b.apps( + fix, + &[domain, fits_motive, rel, well_founded, fits_step, at], + ) }; let fits_fix = fix_at(&mut self.b, packed); let fits_value = self.lambdas(&sig.fits_binders, fits_fix); @@ -423,9 +438,13 @@ impl<'a> Writer<'_, 'a> { let encoded_var = self.b.app(encoder, value_var); let moved_value_statement = self.statement_of(index, sig.arguments, sig.fits_applied, encoded_var); - let value_motive = self - .b - .lam_over("__d", EXPLICIT, lean_result, value_var, moved_value_statement); + let value_motive = self.b.lam_over( + "__d", + EXPLICIT, + lean_result, + value_var, + moved_value_statement, + ); let (definition, definition_args) = self.b.spine(denotation); let definition_name = match self.b.node(definition).clone() { crate::ir::core::CoreNode::Const { n, .. } => n, @@ -454,8 +473,9 @@ impl<'a> Writer<'_, 'a> { let tys = tys.clone(); let fits_name = fits_name.clone(); let (call_name, rel_name) = (call_name.clone(), rel_name.clone()); - self.b - .rewrite_apps(by_value, &mut |b: &mut Builder, name: &str, args: &[Term]| { + self.b.rewrite_apps( + by_value, + &mut |b: &mut Builder, name: &str, args: &[Term]| { if name == call_name { let (_, rest) = args.split_last()?; let head = b.konst(&fits_name); @@ -467,7 +487,8 @@ impl<'a> Writer<'_, 'a> { return Some(b.apps(ihr, &[packed, *evidence])); } None - }) + }, + ) }; let rel_body = self.b.substitute(transported, ¶ms, &projections); let statement_at = |writer: &mut Self, at: Term| -> Vec { @@ -491,7 +512,9 @@ impl<'a> Writer<'_, 'a> { let rel_motive = self .b .lam_over("__x", EXPLICIT, domain, motive_var, motive_body); - let rel_fix = self.b.konst_at("WellFounded.fix", vec![one(), CoreLevel::Zero]); + let rel_fix = self + .b + .konst_at("WellFounded.fix", vec![one(), CoreLevel::Zero]); let rel_fixed = self.b.apps( rel_fix, &[domain, rel_motive, rel, well_founded, rel_step, packed], @@ -507,15 +530,17 @@ impl<'a> Writer<'_, 'a> { // The implementation record of the predicate: direct recursion. let direct = { let (call_name, unsafe_name) = (call_name, unsafe_name); - self.b - .rewrite_apps(fits_raw, &mut |b: &mut Builder, name: &str, args: &[Term]| { + self.b.rewrite_apps( + fits_raw, + &mut |b: &mut Builder, name: &str, args: &[Term]| { if name != call_name { return None; } let (_, rest) = args.split_last()?; let head = b.konst(&unsafe_name); Some(b.apps(head, rest)) - }) + }, + ) }; let unsafe_value = self.lambdas(&sig.fits_binders, direct); let unsafe_ty = self.pis(&sig.fits_binders, bool); diff --git a/crates/lexlean/src/production/kernel/ext.rs b/crates/lexlean/src/production/kernel/ext.rs index 0fe35f1b4..e7d845541 100644 --- a/crates/lexlean/src/production/kernel/ext.rs +++ b/crates/lexlean/src/production/kernel/ext.rs @@ -107,12 +107,9 @@ impl Builder { d, }, CoreNode::Mdata { m, x } => CoreNode::Mdata { m, x: at(&x) }, - CoreNode::Projection { n, s, i, x } => CoreNode::Projection { - n, - s, - i, - x: at(&x), - }, + CoreNode::Projection { n, s, i, x } => { + CoreNode::Projection { n, s, i, x: at(&x) } + } atom @ (CoreNode::BVar { .. } | CoreNode::Sort { .. } | CoreNode::Const { .. } @@ -486,7 +483,9 @@ impl Builder { CoreNode::Nat { v } => v.clone(), CoreNode::String { v } => format!( "{:?}", - v.iter().filter_map(|c| char::from_u32(*c)).collect::() + v.iter() + .filter_map(|c| char::from_u32(*c)) + .collect::() ), CoreNode::App { .. } => { let (head, args) = self.spine(term); @@ -556,18 +555,18 @@ impl Builder { return false; } match &self.nodes[term] { - CoreNode::Const { n, .. } => { - n.starts_with(META) || (free_too && n.starts_with(FREE)) - } + CoreNode::Const { n, .. } => n.starts_with(META) || (free_too && n.starts_with(FREE)), CoreNode::BVar { .. } | CoreNode::Sort { .. } | CoreNode::Nat { .. } | CoreNode::String { .. } => false, CoreNode::App { f, x } => { - self.placeholder_walk(*f, seen, free_too) || self.placeholder_walk(*x, seen, free_too) + self.placeholder_walk(*f, seen, free_too) + || self.placeholder_walk(*x, seen, free_too) } CoreNode::Lambda { t, v, .. } | CoreNode::Forall { t, v, .. } => { - self.placeholder_walk(*t, seen, free_too) || self.placeholder_walk(*v, seen, free_too) + self.placeholder_walk(*t, seen, free_too) + || self.placeholder_walk(*v, seen, free_too) } CoreNode::Let { t, v, body, .. } => { self.placeholder_walk(*t, seen, free_too) @@ -939,7 +938,11 @@ impl Open { /// Solve the placeholders of a binder's type against the type of the /// argument it takes, without giving the argument. pub fn solve_domain(&mut self, b: &Builder, binder: &str, actual: Term) { - if let Some(found) = self.binders.iter().find(|candidate| candidate.name == binder) { + if let Some(found) = self + .binders + .iter() + .find(|candidate| candidate.name == binder) + { let domain = found.domain; b.solve(domain, actual, &mut self.solved); } From cc00f2f0a9c2af363e5ca32926ea8c8115572445 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 17:39:10 +0000 Subject: [PATCH 10/41] #89 stage 3: certificate A recorded in the notation, the SP-15 case and the statement oracle Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- crates/conformance/src/cases/preservation.rs | 321 +++++++++++++++++++ crates/conformance/src/preservation.rs | 156 ++++++++- crates/lexlean/src/api/production.rs | 17 +- crates/lexlean/src/production/preserve.rs | 7 +- 4 files changed, 490 insertions(+), 11 deletions(-) diff --git a/crates/conformance/src/cases/preservation.rs b/crates/conformance/src/cases/preservation.rs index 4de3846f6..01ee717a7 100644 --- a/crates/conformance/src/cases/preservation.rs +++ b/crates/conformance/src/cases/preservation.rs @@ -3719,6 +3719,327 @@ pub fn run(id: &str) { "SP-14 plants refused:\n wrong lemma: {wrong_lemma}\n changed statement: {changed_statement}\n forged crate: {forged_crate}\n added axiom: {audit_refusal}\n lying policy: {lying_policy}" ); } + "SP-15" => { + // Every certificate A of the four corpora is built as a core + // module: the record reads back to the data byte for byte, the + // data audit admits it, every row pins its axioms exactly, and + // the committed records hold it in the notation. + let mut built = 0usize; + let mut rows = 0usize; + let mut notation_bytes = 0usize; + let mut failures: Vec = Vec::new(); + for (name, project) in certified_projects() { + let checked = support::checked_project(&project); + let limits = support::limits(&project); + let linked = linked_modules(&checked); + let hex = checked.semantic_id.to_hex()[..32].to_owned(); + let environment: BTreeSet = + lexlean::production::certificate::LIBRARY_MODULES + .iter() + .map(|module| (*module).to_owned()) + .chain(linked.values().map(|module| module.lean_module.to_owned())) + .collect(); + for (index, root) in roots(&checked).expect("the reports").iter().enumerate() { + let lowered = + lower_root(&linked, &root.module, &root.name, root.report, &limits) + .expect("the root lowers"); + let module = format!("LexLeanPreserve.C{hex}.R{index}"); + let certificate = match lexlean::production::certificate::certificate( + &linked, + &root.module, + &root.name, + root.report, + &lowered, + &module, + limits.max_file_bytes, + ) { + Ok(certificate) => certificate, + Err(diagnostic) => { + failures.push(format!( + "{name} R{index} {}: {}", + root.report.root, diagnostic.message + )); + continue; + } + }; + let data = certificate.data.as_ref().expect("certificate A is data"); + let source = certificate + .source + .as_ref() + .expect("certificate A is recorded in the notation"); + let read = lexlean::sdk::read_module(source).expect("the record reads"); + let canonical = serde_json::to_value(data).expect("the data is JSON"); + assert_eq!( + read, canonical, + "{module}: the record reads back to the data" + ); + preserve::audit_core(data, &environment).expect("the data audit admits it"); + for declaration in &data.declarations { + assert!( + matches!( + declaration.policy, + lexlean::ir::core::CoreAxiomPolicy::Exact(_) + ), + "{module}: `{}` pins its axioms exactly", + declaration.name + ); + } + assert!( + data.declarations + .iter() + .any(|row| row.name == format!("{module}.root")), + "{module}: the root theorem is a row" + ); + built += 1; + rows += data.declarations.len(); + notation_bytes += source.len(); + } + } + eprintln!( + "SP-15: {built} certificates A built as data, {rows} rows, {notation_bytes} bytes of notation" + ); + assert!( + failures.is_empty(), + "every certificate A is built as data:\n{}", + failures.join("\n") + ); + // The committed records hold every certificate A in the notation. + for (name, _) in certified_projects() { + let base = repo_root() + .join("examples") + .join(name) + .join("expected/verify/preserve"); + let record: serde_json::Value = serde_json::from_slice( + &std::fs::read(base.join("preservation.json").as_std_path()) + .expect("a committed record"), + ) + .expect("the record is JSON"); + for row in record["roots"].as_array().expect("roots") { + let module = row["module"].as_str().expect("a module"); + let path = base.join(preserve::notation_path(module)); + assert!( + path.as_std_path().is_file(), + "{name}: certificate A `{module}` is recorded in the notation at {path}" + ); + } + } + if !support::lean_backed("SP-15") { + return; + } + // The production example, certified end to end with every + // certificate A as data: every module compiles, replays, and the + // audit sees exactly the axioms each row pins. + let project = P::copy_example("production"); + let certified = preservation::certificates(&project); + let workspace = preservation::staged(&project, &certified); + let root = tempfile::Builder::new() + .prefix("lexlean-sp15-") + .tempdir() + .expect("tempdir"); + let checked = preservation::check(&workspace, root.path()); + assert!( + checked.failure.is_none(), + "every certificate as data verifies under the pinned Lean: {:?}", + checked.failure + ); + let all: Vec = certified + .iter() + .flat_map(preservation::Certified::all) + .collect(); + audit(&checked.audit_output, &all).expect("the audit, every row's axioms included"); + // Four plants in the data of one certificate A, each refused: a + // wrong lemma application, a changed statement, a forged program + // literal, and an added axiom (refused before Lean by the data + // audit, and, as a lying policy, by the audit module). + let certificate = &certified + .iter() + .find(|entry| { + entry.certificate.data.as_ref().is_some_and(|data| { + data.nodes.iter().any(|node| { + matches!(node, lexlean::ir::core::CoreNode::Const { n, .. } + if n == "LexLeanPreservation.prim_natAdd") + }) + }) + }) + .expect("a root that adds") + .certificate; + let data = certificate.data.as_ref().expect("certificate A is data"); + let rename = |data: &lexlean::ir::core::CoreModule, from: &str, to: &str| { + let mut planted = data.clone(); + let mut renamed = 0; + for node in &mut planted.nodes { + if let lexlean::ir::core::CoreNode::Const { n, .. } = node { + if n == from { + *n = to.to_owned(); + renamed += 1; + } + } + } + assert!(renamed > 0, "the plant renames `{from}`"); + planted + }; + let refused = |label: &str, planted: &lexlean::ir::core::CoreModule| { + let module = format!("{}Plant{label}", certificate.module); + let text = lexlean::backend::core::lean_text_legacy(planted, &module) + .expect("the planted module renders"); + let output = preservation::compile_text(root.path(), &module, &text); + let joined = format!( + "{}{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + assert!( + !output.status.success() && joined.contains("error"), + "{label}: the planted certificate A is refused by Lean: {joined}" + ); + joined.lines().take(3).collect::>().join("\n ") + }; + let wrong_lemma = refused( + "WrongLemma", + &rename( + data, + "LexLeanPreservation.prim_natAdd", + "LexLeanPreservation.prim_natMul", + ), + ); + let changed_statement = + refused("ChangedStatement", &rename(data, "Bool.true", "Bool.false")); + // A forged program literal: one literal of the program's row, + // rebuilt on a fresh path so no other row sees it. + let forged = { + let program = data + .declarations + .iter() + .find(|row| row.name == format!("{}.__prog", certificate.module)) + .and_then(|row| row.value) + .expect("the program row"); + let mut planted = data.clone(); + let mut path = Vec::new(); + let mut at = program; + loop { + match &planted.nodes[at] { + lexlean::ir::core::CoreNode::Nat { .. } => break, + lexlean::ir::core::CoreNode::App { f, x } => { + path.push(at); + // The arguments hold the literals; the head holds the constructor. + at = *x; + let _ = f; + } + other => panic!( + "the program's row has no literal along its arguments: {other:?}" + ), + } + } + let literal = match &planted.nodes[at] { + lexlean::ir::core::CoreNode::Nat { v } => { + v.parse::().expect("a small literal") + 1 + } + _ => unreachable!("the loop stops at a literal"), + }; + planted.nodes.push(lexlean::ir::core::CoreNode::Nat { + v: literal.to_string(), + }); + let mut replacement = planted.nodes.len() - 1; + for parent in path.iter().rev() { + let node = match planted.nodes[*parent].clone() { + lexlean::ir::core::CoreNode::App { f, .. } => { + lexlean::ir::core::CoreNode::App { f, x: replacement } + } + other => other, + }; + planted.nodes.push(node); + replacement = planted.nodes.len() - 1; + } + for row in &mut planted.declarations { + if row.name == format!("{}.__prog", certificate.module) { + row.value = Some(replacement); + } + } + planted + }; + let forged_program = refused("ForgedProgram", &forged); + // An added axiom: `sorryAx` as the root's proof is refused by the + // data audit before any Lean runs. + let mut with_axiom = data.clone(); + with_axiom.nodes.push(lexlean::ir::core::CoreNode::Const { + n: "sorryAx".to_owned(), + u: vec![lexlean::ir::core::CoreLevel::Zero], + }); + let last = with_axiom.nodes.len() - 1; + let theorem = format!("{}.root", certificate.module); + for row in &mut with_axiom.declarations { + if row.name == theorem { + row.value = Some(last); + } + } + let environment: BTreeSet = workspace + .files + .iter() + .map(|file| file.module.clone()) + .collect(); + let audit_refusal = preserve::audit_core(&with_axiom, &environment) + .expect_err("the data audit refuses the added axiom"); + assert!( + audit_refusal.contains("sorryAx"), + "the data audit names the forbidden constant: {audit_refusal}" + ); + // A row whose policy lies about its axioms compiles, and the audit + // module refuses it: the policies are checked, not trusted. + let mut lying = data.clone(); + let liar = lying + .declarations + .iter_mut() + .find(|row| { + matches!(&row.policy, lexlean::ir::core::CoreAxiomPolicy::Exact(axioms) + if !axioms.is_empty()) + }) + .expect("a row that pins an axiom"); + liar.policy = lexlean::ir::core::CoreAxiomPolicy::Exact(Vec::new()); + let mut lying_certificate = certificate.clone(); + lying_certificate.module = format!("{}PlantLyingPolicy", certificate.module); + lying_certificate.text = + lexlean::backend::core::lean_text_legacy(&lying, &lying_certificate.module) + .expect("the planted module renders"); + lying_certificate.data = Some(lying); + let output = preservation::compile_text( + root.path(), + &lying_certificate.module, + &lying_certificate.text, + ); + assert!( + output.status.success(), + "a lying policy is not Lean's to refuse: {}", + String::from_utf8_lossy(&output.stderr) + ); + let rendered = support::rendered(&project); + let user: Vec<(String, String)> = rendered + .modules + .iter() + .map(|module| (module.lean_module.clone(), module.lean_text.clone())) + .collect(); + let planted_workspace = + preserve::workspace(&user, std::slice::from_ref(&lying_certificate)) + .expect("the planted workspace stages"); + let audit_output = preservation::compile_text( + root.path(), + &format!("{}Plant", preserve::AUDIT_MODULE), + &planted_workspace.audit.text, + ); + let audit_text = format!( + "{}{}", + String::from_utf8_lossy(&audit_output.stdout), + String::from_utf8_lossy(&audit_output.stderr) + ); + let lying_policy = audit(&audit_text, std::slice::from_ref(&lying_certificate)) + .expect_err("the audit refuses a row whose policy lies"); + assert!( + lying_policy.contains("its policy pins"), + "the audit names the lying row: {lying_policy}" + ); + eprintln!( + "SP-15 plants refused:\n wrong lemma: {wrong_lemma}\n changed statement: {changed_statement}\n forged program: {forged_program}\n added axiom: {audit_refusal}\n lying policy: {lying_policy}" + ); + } _ => panic!("no preservation case is wired for {id}"), } } diff --git a/crates/conformance/src/preservation.rs b/crates/conformance/src/preservation.rs index 56d77e4c7..e24c6e578 100644 --- a/crates/conformance/src/preservation.rs +++ b/crates/conformance/src/preservation.rs @@ -2927,8 +2927,162 @@ mod dev { } None => { audit(&result.audit_output, &built).expect("the axiom audit"); - eprintln!("{} certificates A: compiled, replayed, audited", built.len()); + eprintln!( + "{} certificates A: compiled, replayed, audited", + built.len() + ); + } + } + } + + /// The statement oracle of certificate A (`SP-15`): for each selected root + /// (`LEXLEAN_EXAMPLE`, `LEXLEAN_ROOTS` as in [`dev_certificate_a`]), + /// certificate A as data is compiled beside the committed text + /// certificate A of the same root with its namespace renamed to + /// `.Old`, and an oracle module compares, for every row both + /// hold, the type of the data's row, its own names read as the old + /// module's, with the type of the old row by `Lean.Meta.isDefEq`. The + /// bodies of the definitions are compared and counted, not required: a + /// recursive width predicate is a different, equally proved, definition. + #[test] + #[ignore = "the oracle behind tests/preservation-certificates/A-STATEMENTS.md"] + fn dev_oracle_a() { + let example = std::env::var("LEXLEAN_EXAMPLE").unwrap_or_else(|_| "production".to_owned()); + let only: Option> = std::env::var("LEXLEAN_ROOTS").ok().map(|list| { + list.split(',') + .filter_map(|item| item.trim().parse().ok()) + .collect() + }); + let project = P::copy_example(&example); + let checked = support::checked_project(&project); + let modules = linked_modules(&checked); + let hex32: String = checked.semantic_id.to_hex()[..32].to_owned(); + let limits = support::limits(&project); + let mut built = Vec::new(); + for (index, root) in roots(&checked) + .expect("the eligibility reports") + .iter() + .enumerate() + { + if only.as_ref().is_some_and(|list| !list.contains(&index)) { + continue; + } + let lowered = lower_root(&modules, &root.module, &root.name, root.report, &limits) + .expect("lowering"); + let module = format!("LexLeanPreserve.C{hex32}.R{index}"); + match certificate( + &modules, + &root.module, + &root.name, + root.report, + &lowered, + &module, + limits.max_file_bytes, + ) { + Ok(certificate) => built.push((index, certificate)), + Err(diagnostic) => { + eprintln!("R{index} {}: {}", root.report.root, diagnostic.message); + } } } + let rendered = support::rendered(&project); + let user: Vec<(String, String)> = rendered + .modules + .iter() + .map(|module| (module.lean_module.clone(), module.lean_text.clone())) + .collect(); + let certificates: Vec = built + .iter() + .map(|(_, certificate)| certificate.clone()) + .collect(); + let staged = workspace(&user, &certificates).expect("the workspace stages"); + let scratch = tempfile::Builder::new() + .prefix("lexlean-oracle-a-") + .tempdir() + .expect("tempdir"); + let result = check(&staged, scratch.path()); + if let Some((module, output)) = &result.failure { + let head: String = output.lines().take(40).collect::>().join("\n"); + panic!("`{module}` was rejected:\n{head}"); + } + let base = support::repo_root() + .join("examples") + .join(&example) + .join("expected/verify/preserve/LexLeanPreserve") + .join(format!("C{hex32}")); + let mut compared = 0usize; + let mut bodies = (0usize, 0usize); + for (index, certificate) in &built { + let module = &certificate.module; + let committed = + std::fs::read_to_string(base.join(format!("R{index}.lean")).as_std_path()) + .unwrap_or_else(|error| { + panic!("the committed certificate of R{index}: {error}") + }); + let old = format!("{module}.Old"); + let renamed = committed + .replace( + &format!("namespace {module}\n"), + &format!("namespace {old}\n"), + ) + .replace(&format!("end {module}"), &format!("end {old}")); + assert_ne!(renamed, committed, "R{index}: the namespace is renamed"); + let output = compile_text(scratch.path(), &old, &renamed); + assert!( + output.status.success(), + "R{index}: the committed text certificate compiles: {}", + joined(&output) + ); + let data = certificate.data.as_ref().expect("certificate A is data"); + let rows: Vec = data + .declarations + .iter() + .map(|row| { + row.name + .strip_prefix(&format!("{module}.")) + .unwrap_or(&row.name) + .to_owned() + }) + .collect(); + let pairs: Vec = rows + .iter() + .map(|row| format!("(`{module}.{row}, `{old}.{row})")) + .collect(); + let oracle = format!( + "import {module}\nimport {old}\nimport Lean\nopen Lean Meta\n\ + def swapRoot (new old : Name) (e : Expr) : Expr :=\n e.replace fun\n | .const n us =>\n if new.isPrefixOf n && !old.isPrefixOf n then some (.const (n.replacePrefix new old) us) else none\n | _ => none\n\ + run_cmd Lean.Elab.Command.liftTermElabM do\n let pairs : List (Name × Name) := [{}]\n let mut types : Nat := 0\n let mut same : Nat := 0\n let mut total : Nat := 0\n for (new, old) in pairs do\n let env ← getEnv\n let some n := env.find? new | throwError \"{{new}}: not in the environment\"\n let some o := env.find? old | throwError \"{{old}}: the committed certificate lacks the row\"\n unless (← isDefEq (swapRoot `{module} `{old} n.type) o.type) do\n throwError \"{{new}}: the statement differs from {{old}}\"\n types := types + 1\n if let (some a, some b) := (n.value?, o.value?) then\n total := total + 1\n if (← isDefEq (swapRoot `{module} `{old} a) b) then same := same + 1\n logInfo m!\"SP-15 oracle: {{types}} statements equal; bodies {{same}} of {{total}}\"\n", + pairs.join(", ") + ); + let name = format!("{module}.Oracle"); + let output = compile_text(scratch.path(), &name, &oracle); + let text = joined(&output); + assert!( + output.status.success(), + "R{index}: the statements of {module} are the committed certificate's:\n{}", + text.lines().take(20).collect::>().join("\n") + ); + eprintln!("R{index} {module}: {}", text.trim()); + compared += rows.len(); + if let Some(found) = text.split("bodies ").nth(1) { + let mut numbers = found.split(" of "); + if let (Some(same), Some(total)) = ( + numbers.next().and_then(|n| n.trim().parse::().ok()), + numbers + .next() + .and_then(|n| n.split_whitespace().next()) + .and_then(|n| n.parse::().ok()), + ) { + bodies.0 += same; + bodies.1 += total; + } + } + } + eprintln!( + "SP-15 oracle: {} certificates A, {compared} rows, definition bodies equal {} of {}", + built.len(), + bodies.0, + bodies.1 + ); } } diff --git a/crates/lexlean/src/api/production.rs b/crates/lexlean/src/api/production.rs index 8caa6c2a0..da5361c3d 100644 --- a/crates/lexlean/src/api/production.rs +++ b/crates/lexlean/src/api/production.rs @@ -1289,7 +1289,9 @@ fn build_inner( ))); } let program = add(format!("evidence/program/R{index}.json"), program_bytes); - let a_path = certificate_path(&preserved.module); + // Certificates A and E are kernel-term data, recorded in the + // authoring notation (SPEC.md §17.17); B is Lean text. + let a_path = crate::production::preserve::notation_path(&preserved.module); let certificate_a = CertificateRef { role: CertificateRole::A, module: preserved.module.clone(), @@ -1341,11 +1343,11 @@ fn build_inner( &rendering.composed.theorem, ), ] { - // Certificate E is kernel-term data, recorded in the authoring - // notation (SPEC.md §17.17); A and B are Lean text. let path = match role { - CertificateRole::E => crate::production::preserve::notation_path(module), - CertificateRole::A | CertificateRole::B => certificate_path(module), + CertificateRole::A | CertificateRole::E => { + crate::production::preserve::notation_path(module) + } + CertificateRole::B => certificate_path(module), }; certificates.push(CertificateRef { role, @@ -1836,10 +1838,9 @@ fn replay_files(files: &[(String, Vec)]) -> Result + CertificateRole::A | CertificateRole::E => crate::production::preserve::notation_path(module), - CertificateRole::A | CertificateRole::B => - certificate_path(module), + CertificateRole::B => certificate_path(module), } ) && ( diff --git a/crates/lexlean/src/production/preserve.rs b/crates/lexlean/src/production/preserve.rs index 3b08d6c4c..857e9371a 100644 --- a/crates/lexlean/src/production/preserve.rs +++ b/crates/lexlean/src/production/preserve.rs @@ -1636,7 +1636,10 @@ pub fn record(roots: &[CertifiedRoot]) -> crate::artifact::canonical_json::Json ), ("module", Json::Str(root.certificate.module.clone())), ("theorem", Json::Str(root.certificate.theorem.clone())), - ("byte_length", Json::from_usize(root.certificate.text.len())), + ( + "byte_length", + Json::from_usize(recorded_bytes(&root.certificate).len()), + ), ( "program", Json::object(vec![ @@ -1650,7 +1653,7 @@ pub fn record(roots: &[CertifiedRoot]) -> crate::artifact::canonical_json::Json ( "sha256", Json::Str( - Sha256Digest::of(root.certificate.text.as_bytes()).to_hex(), + Sha256Digest::of(recorded_bytes(&root.certificate)).to_hex(), ), ), ( From 0abe254b1369d87e4e44e6a7905462e7886a8306 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 17:55:21 +0000 Subject: [PATCH 11/41] Certificate A: collection templates and key comparisons as kernel terms The library's template lemmas are applied at the runtime's functions with every clause hypothesis discharged by a proof term: computed clauses by reflexivity, comparison clauses by transporting F(compare k o) = F(compare k o) along the hypothesis, F being the runtime function's body with the scrutinee abstracted. The denotation of the collection primitives and the collections runtime's axiom table are added. Co-Authored-By: Claude Sonnet 5.5 --- .../src/production/certificate/data_a.rs | 41 + .../production/certificate/data_a/calls.rs | 19 +- .../production/certificate/data_a/clauses.rs | 917 ++++++++++++++++++ .../src/production/certificate/data_a/src.rs | 4 +- .../certificate/data_a/templates.rs | 900 ++++++++++++++++- 5 files changed, 1862 insertions(+), 19 deletions(-) create mode 100644 crates/lexlean/src/production/certificate/data_a/clauses.rs diff --git a/crates/lexlean/src/production/certificate/data_a.rs b/crates/lexlean/src/production/certificate/data_a.rs index 5648a718e..930e498ba 100644 --- a/crates/lexlean/src/production/certificate/data_a.rs +++ b/crates/lexlean/src/production/certificate/data_a.rs @@ -34,6 +34,7 @@ use crate::ir::semantic::SemanticType; mod boundary; mod calls; +mod clauses; mod lean; mod encoders; mod matching; @@ -277,6 +278,11 @@ impl<'g, 'a> Writer<'g, 'a> { return Ok(runtime_axioms(rest)); } } + if let Some((module, rest)) = name.split_once(".LexLeanCollections.") { + if self.runtime_modules.contains(module) { + return Ok(collections_axioms(rest)); + } + } Ok(Vec::new()) } @@ -595,3 +601,38 @@ fn runtime_axioms(name: &str) -> Vec { _ => Vec::new(), } } + +/// The axioms of a declaration of the collections runtime every generated +/// module carries (`backend::semantic`), by its name below +/// `LexLeanCollections`, as `#print axioms` reports them, measured with +/// `Lean.collectAxioms` over a compiled module. Every declaration not named +/// here depends on none. The audit module prints the axioms of every row, so +/// a runtime that changes disagrees with this table at the first certificate. +fn collections_axioms(name: &str) -> Vec { + let propext = || vec!["propext".to_owned()]; + match name { + "compareCodes" + | "compareCodes._f" + | "compareCodes._sparseCasesOn_1" + | "compareCodes._sunfold" + | "compareCodes._unsafe_rec" + | "compareCodes.match_1" + | "graphReachable" + | "instKeyBool" + | "instKeyProd" + | "lessThan" + | "lessThan._sparseCasesOn_1" + | "lessThan.match_1" + | "reachableFrom" + | "reachableFrom._f" + | "reachableFrom._sparseCasesOn_1" + | "reachableFrom._sunfold" + | "reachableFrom._unsafe_rec" + | "reachableFrom.match_1" => propext(), + "instKeyString" => ["Classical.choice", "Quot.sound", "propext"] + .iter() + .map(|axiom| (*axiom).to_owned()) + .collect(), + _ => Vec::new(), + } +} diff --git a/crates/lexlean/src/production/certificate/data_a/calls.rs b/crates/lexlean/src/production/certificate/data_a/calls.rs index d60d3520f..9fbed9f0d 100644 --- a/crates/lexlean/src/production/certificate/data_a/calls.rs +++ b/crates/lexlean/src/production/certificate/data_a/calls.rs @@ -11,15 +11,16 @@ use crate::production::kernel::{Given, Term}; use crate::production::source::Source; /// A function-typed argument with its companions. -struct FunctionArgument { - value: Term, - fits: Term, - index: Term, - captures: Term, - relation: Term, - proof: Pf, +#[derive(Debug, Clone, Copy)] +pub(super) struct FunctionArgument { + pub(super) value: Term, + pub(super) fits: Term, + pub(super) index: Term, + pub(super) captures: Term, + pub(super) relation: Term, + pub(super) proof: Pf, /// The function the program numbered it (unused for a parameter). - number: u64, + pub(super) number: u64, } /// The parameter and result types of a closed function type. @@ -166,7 +167,7 @@ impl<'a> Writer<'_, 'a> { /// A function-typed argument's value and companions; `None` for a /// first-order argument. - fn function_argument( + pub(super) fn function_argument( &mut self, argument: &SemanticTerm, expr: &'a Expr, diff --git a/crates/lexlean/src/production/certificate/data_a/clauses.rs b/crates/lexlean/src/production/certificate/data_a/clauses.rs new file mode 100644 index 000000000..fc24ec528 --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/clauses.rs @@ -0,0 +1,917 @@ +//! The collections runtime as kernel terms (SPEC.md §17.17.3): its constants +//! and key instances, the denotation of a collection primitive, and the +//! clause hypotheses of the library's templates, each discharged by a proof +//! term. +//! +//! A clause states what one runtime function does when a comparison came out +//! one way: `∀ binders, compare k o = c → f args = rhs`. The runtime +//! function's body is a match on the comparison, so the statement follows by +//! rewriting the comparison in the one equation `F (compare k o) = F (compare +//! k o)`, where `F` is the function with the scrutinee abstracted. `F` is +//! written here with the eliminator of the scrutinee's type, or with the +//! runtime's own matcher where Lean compiled the match to a sparse eliminator, +//! whose branches an eliminator written by hand is not definitionally equal +//! to on an unknown scrutinee. The kernel then unfolds the runtime function +//! and takes the eliminator's step at the hypothesis's constructor. + +use super::{Pf, Writer, EXPLICIT}; +use crate::ir::core::{CoreBinderInfo, CoreLevel, CoreNode}; +use crate::ir::semantic::SemanticType; +use crate::production::kernel::{Given, Term}; +use crate::production::source::Site; + +/// The universe level 1, of the types the runtime's functions return. +fn one() -> CoreLevel { + CoreLevel::Succ(Box::new(CoreLevel::Zero)) +} + +/// An argument of a library lemma: a given one, or a clause proof over the +/// number of binders the clause quantifies before its hypothesis. +#[derive(Clone, Copy)] +pub(super) enum Arg { + Pass(Given), + Clause(usize, Form), +} + +impl Arg { + pub(super) fn term(term: Term) -> Self { + Self::Pass(Given::Term(term)) + } + + pub(super) fn proof(pf: Pf) -> Self { + Self::Pass(Given::Proof(pf.proof, pf.ty)) + } + + pub(super) const fn refl() -> Self { + Self::Pass(Given::Refl) + } + + /// `count` hypotheses discharged by computation. + pub(super) fn refls(count: usize) -> Vec { + vec![Self::refl(); count] + } +} + +/// The function a clause is about, with the terms its right sides name. +#[derive(Clone, Copy)] +pub(super) enum Form { + /// `insertEntry κ ν inst`; binders key, value, other, stored, rest. + InsertEntry { + kappa: Term, + nu: Term, + function: Term, + }, + /// `removeEntry κ ν inst`; binders key, other, stored, rest. + RemoveEntry { + kappa: Term, + nu: Term, + function: Term, + }, + /// `lookupEntry κ ν inst`; binders key, other, stored, rest. + LookupEntry { nu: Term, function: Term }, + /// `insertElement κ inst`; binders key, other, rest. + InsertElement { kappa: Term, function: Term }, + /// `removeElement κ inst`; binders key, other, rest. + RemoveElement { kappa: Term, function: Term }, + /// `containsElement κ inst`; binders key, other, rest. + ContainsElement { function: Term }, + /// `compareCodes`; binders left, lefts, right, rights. The matcher is the + /// runtime's `compareCodes.match_1`. + CompareCodes { matcher: Term, function: Term }, + /// The pair key order; binders left, right. `second` compares the second + /// components. + PairKey { + matcher: Term, + alpha: Term, + beta: Term, + second: Term, + }, + /// `lessThan`; binders left, right. + LessThan { matcher: Term }, + /// `reachableFrom κ inst graph`; binders fuel, frontier, seen, and for a + /// non-empty round also the head and tail. `insert` is `insertElement κ + /// inst`. + Reach { + matcher: Term, + kappa: Term, + reach: Term, + insert: Term, + }, + /// `topological κ inst graph` at a frontier that is stuck; binders fuel, + /// head, tail, order. + TopoStuck { + kappa: Term, + topo: Term, + remove: Term, + }, + /// `topological κ inst graph` at a round that has a ready node; binders + /// fuel, remaining, order, ready, rest. + TopoNext { + kappa: Term, + topo: Term, + remove: Term, + }, + /// `iterateUntil σ step`; binders fuel, state, and for a step that + /// continues also the next state. + IterateUntil { sigma: Term, iterate: Term }, +} + +impl Writer<'_, '_> { + // --- Constants -------------------------------------------------------- + + /// The name of a declaration of the collections runtime of the module + /// `site` is read in. + pub(super) fn collections_name(&self, site: &Site, name: &str) -> Result { + let module = self.gen.source.lean_module(&site.module)?; + Ok(format!("{module}.LexLeanCollections.{name}")) + } + + /// A declaration of the collections runtime. + pub(super) fn collections(&mut self, site: &Site, name: &str) -> Result { + let full = self.collections_name(site, name)?; + Ok(self.b.konst(&full)) + } + + /// A matcher of the collections runtime, which is universe polymorphic in + /// its motive; the motives here return types in `Type`. + fn matcher(&mut self, site: &Site, name: &str) -> Result { + let full = self.collections_name(site, name)?; + Ok(self.b.konst_at(&full, vec![one()])) + } + + /// The runtime's `compareCodes.match_1`, its `lessThan.match_1`, and its + /// `reachableFrom.match_1`. + fn code_matcher(&mut self, site: &Site) -> Result { + self.matcher(site, "compareCodes.match_1") + } + + pub(super) fn less_matcher(&mut self, site: &Site) -> Result { + self.matcher(site, "lessThan.match_1") + } + + pub(super) fn reach_matcher(&mut self, site: &Site) -> Result { + self.matcher(site, "reachableFrom.match_1") + } + + // --- Key orders ------------------------------------------------------- + + /// The `Key` instance of a key type, as the pinned Lean selects it. + pub(super) fn key_instance(&mut self, site: &Site, ty: &SemanticType) -> Result { + let name = match ty { + SemanticType::Nat => "instKeyNat", + SemanticType::Int => "instKeyInt", + SemanticType::Bool => "instKeyBool", + SemanticType::Int8 => "instKeyInt8", + SemanticType::Int16 => "instKeyInt16", + SemanticType::Int32 => "instKeyInt32", + SemanticType::Int64 => "instKeyInt64", + SemanticType::UInt8 => "instKeyUInt8", + SemanticType::UInt16 => "instKeyUInt16", + SemanticType::UInt32 => "instKeyUInt32", + SemanticType::UInt64 => "instKeyUInt64", + SemanticType::String => "instKeyString", + SemanticType::Product { left, right } => { + let alpha = self.lean_ty(left)?; + let beta = self.lean_ty(right)?; + let first = self.key_instance(site, left)?; + let second = self.key_instance(site, right)?; + let head = self.collections(site, "instKeyProd")?; + return Ok(self.b.apps(head, &[alpha, beta, first, second])); + } + SemanticType::Type + | SemanticType::Parameter { name: _ } + | SemanticType::Prop + | SemanticType::Unit + | SemanticType::Bytes + | SemanticType::Ordering + | SemanticType::Option { value: _ } + | SemanticType::Result { ok: _, error: _ } + | SemanticType::List { element: _ } + | SemanticType::Named { + member: _, + arguments: _, + } + | SemanticType::Function { + parameters: _, + result: _, + } + | SemanticType::Map { key: _, value: _ } + | SemanticType::Set { element: _ } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure => { + return Err(format!( + "`{}` has no key order", + self.gen.source.type_text(ty) + )); + } + }; + self.collections(site, name) + } + + /// The source key order of `ty`: the runtime's `Key.compare` at its + /// instance. + pub(super) fn key_order(&mut self, site: &Site, ty: &SemanticType) -> Result { + let lean = self.lean_ty(ty)?; + let instance = self.key_instance(site, ty)?; + let compare = self.collections(site, "Key.compare")?; + Ok(self.b.apps(compare, &[lean, instance])) + } + + /// A runtime function at the type arguments and instance the elaborator + /// gives it. + pub(super) fn collections_at( + &mut self, + site: &Site, + name: &str, + leading: &[Term], + ) -> Result { + let head = self.collections(site, name)?; + Ok(self.b.apps(head, leading)) + } + + /// The proof that the realization's comparison agrees with the key order + /// of `ty`. + pub(super) fn key_spec(&mut self, site: &Site, ty: &SemanticType) -> Result { + let order = self.key_order(site, ty)?; + let scalar = match ty { + SemanticType::Nat => Some("keySpec_nat"), + SemanticType::Int => Some("keySpec_int"), + SemanticType::Bool => Some("keySpec_bool"), + SemanticType::Int8 => Some("keySpec_i8"), + SemanticType::Int16 => Some("keySpec_i16"), + SemanticType::Int32 => Some("keySpec_i32"), + SemanticType::Int64 => Some("keySpec_i64"), + SemanticType::UInt8 => Some("keySpec_u8"), + SemanticType::UInt16 => Some("keySpec_u16"), + SemanticType::UInt32 => Some("keySpec_u32"), + SemanticType::UInt64 => Some("keySpec_u64"), + SemanticType::Type + | SemanticType::Parameter { name: _ } + | SemanticType::Prop + | SemanticType::Unit + | SemanticType::String + | SemanticType::Bytes + | SemanticType::Ordering + | SemanticType::Option { value: _ } + | SemanticType::Result { ok: _, error: _ } + | SemanticType::List { element: _ } + | SemanticType::Named { + member: _, + arguments: _, + } + | SemanticType::Product { left: _, right: _ } + | SemanticType::Function { + parameters: _, + result: _, + } + | SemanticType::Map { key: _, value: _ } + | SemanticType::Set { element: _ } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure => None, + }; + if let Some(lemma) = scalar { + return self.lemma_args(lemma, &[], &[Arg::term(order), Arg::refl()]); + } + match ty { + SemanticType::String => { + let codes = self.collections(site, "compareCodes")?; + let matcher = self.code_matcher(site)?; + let clause = Arg::Clause( + 4, + Form::CompareCodes { + matcher, + function: codes, + }, + ); + self.lemma_args( + "keySpec_string", + &[], + &[ + Arg::term(codes), + Arg::refl(), + Arg::refl(), + Arg::refl(), + clause, + clause, + clause, + Arg::term(order), + Arg::refl(), + ], + ) + } + SemanticType::Product { left, right } => { + let first = self.key_spec(site, left)?; + let second = self.key_spec(site, right)?; + let alpha = self.lean_ty(left)?; + let beta = self.lean_ty(right)?; + let compare = self.key_order(site, right)?; + let matcher = self.code_matcher(site)?; + let clause = Arg::Clause( + 2, + Form::PairKey { + matcher, + alpha, + beta, + second: compare, + }, + ); + self.lemma_args( + "keySpec_pair", + &[], + &[ + Arg::proof(first), + Arg::proof(second), + Arg::term(order), + clause, + clause, + clause, + ], + ) + } + SemanticType::Nat + | SemanticType::Int + | SemanticType::Bool + | SemanticType::Int8 + | SemanticType::Int16 + | SemanticType::Int32 + | SemanticType::Int64 + | SemanticType::UInt8 + | SemanticType::UInt16 + | SemanticType::UInt32 + | SemanticType::UInt64 + | SemanticType::Type + | SemanticType::Parameter { name: _ } + | SemanticType::Prop + | SemanticType::Unit + | SemanticType::Bytes + | SemanticType::Ordering + | SemanticType::Option { value: _ } + | SemanticType::Result { ok: _, error: _ } + | SemanticType::List { element: _ } + | SemanticType::Named { + member: _, + arguments: _, + } + | SemanticType::Function { + parameters: _, + result: _, + } + | SemanticType::Map { key: _, value: _ } + | SemanticType::Set { element: _ } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure => Err(format!( + "`{}` has no key order", + self.gen.source.type_text(ty) + )), + } + } + + // --- Operation records of the library --------------------------------- + + /// The set-runtime clauses of key type `ty`: `SetOps` at the runtime's + /// insertion, removal, and membership. + pub(super) fn set_ops(&mut self, site: &Site, ty: &SemanticType) -> Result { + let kappa = self.lean_ty(ty)?; + let instance = self.key_instance(site, ty)?; + let order = self.key_order(site, ty)?; + let insert = self.collections_at(site, "insertElement", &[kappa, instance])?; + let remove = self.collections_at(site, "removeElement", &[kappa, instance])?; + let contains = self.collections_at(site, "containsElement", &[kappa, instance])?; + let insertion = Form::InsertElement { + kappa, + function: insert, + }; + let removal = Form::RemoveElement { + kappa, + function: remove, + }; + let membership = Form::ContainsElement { function: contains }; + let mut args = vec![Arg::refl()]; + args.extend(vec![Arg::Clause(3, insertion); 3]); + args.push(Arg::refl()); + args.extend(vec![Arg::Clause(3, removal); 3]); + args.push(Arg::refl()); + args.extend(vec![Arg::Clause(3, membership); 3]); + self.lemma_args( + "SetOps.mk", + &[ + ("κ", kappa), + ("cmp", order), + ("ins", insert), + ("rem", remove), + ("con", contains), + ], + &args, + ) + } + + /// The lookup-runtime clauses of key type `key` and value type `value`. + pub(super) fn look_ops( + &mut self, + site: &Site, + key: &SemanticType, + value: &SemanticType, + ) -> Result { + let kappa = self.lean_ty(key)?; + let nu = self.lean_ty(value)?; + let instance = self.key_instance(site, key)?; + let order = self.key_order(site, key)?; + let lookup = self.collections_at(site, "lookupEntry", &[kappa, nu, instance])?; + let form = Form::LookupEntry { + nu, + function: lookup, + }; + let mut args = vec![Arg::refl()]; + args.extend(vec![Arg::Clause(4, form); 3]); + self.lemma_args( + "LookOps.mk", + &[("κ", kappa), ("ν", nu), ("cmp", order), ("look", lookup)], + &args, + ) + } + + /// The topological-sort clauses of key type `ty` on a graph `graph`. + pub(super) fn topo_ops( + &mut self, + site: &Site, + ty: &SemanticType, + graph: Term, + ) -> Result { + let kappa = self.lean_ty(ty)?; + let instance = self.key_instance(site, ty)?; + let successors = self.collections_at(site, "graphSuccessors", &[kappa, instance, graph])?; + let contains = self.collections_at(site, "containsElement", &[kappa, instance])?; + let remove = self.collections_at(site, "removeElement", &[kappa, instance])?; + let topo = self.collections_at(site, "topological", &[kappa, instance, graph])?; + let stuck = Form::TopoStuck { + kappa, + topo, + remove, + }; + let next = Form::TopoNext { + kappa, + topo, + remove, + }; + self.lemma_args( + "TopoOps.mk", + &[ + ("κ", kappa), + ("sx", successors), + ("con", contains), + ("rem", remove), + ("topo", topo), + ], + &[ + Arg::refl(), + Arg::refl(), + Arg::refl(), + Arg::Clause(4, stuck), + Arg::Clause(5, next), + ], + ) + } + + // --- Applying a lemma with clause arguments --------------------------- + + /// A library lemma applied to arguments, a clause argument proved from the + /// statement its binder carries once the arguments before it are known. + pub(super) fn lemma_args( + &mut self, + name: &str, + hints: &[(&str, Term)], + args: &[Arg], + ) -> Result { + let mut open = self.open(name, hints)?; + for arg in args { + match *arg { + Arg::Pass(given) => open.give(&mut self.b, given)?, + Arg::Clause(binders, form) => { + let domain = open.domain(&mut self.b); + let proof = self + .clause_proof(domain, binders, form) + .map_err(|error| format!("a clause of `{name}`: {error}"))?; + open.give(&mut self.b, Given::Term(proof))?; + } + } + } + let applied = open.finish(&mut self.b)?; + Ok(Pf { + proof: applied.term, + ty: applied.ty, + }) + } + + /// The proof of a clause statement `domain`: `binders` quantifiers, then + /// the hypothesis `scrutinee = case`, then the equation `lhs = rhs`. + /// + /// The proof is `fun .. h => Eq.rec (F scrutinee = F scrutinee) h`, whose + /// type `F scrutinee = F case` the kernel finds to be the statement: `F + /// scrutinee` is the left side by unfolding the runtime function, and `F + /// case` the right side by the eliminator's step on a constructor. + fn clause_proof(&mut self, domain: Term, binders: usize, form: Form) -> Result { + let mut opened: Vec<(String, CoreBinderInfo, Term, Term)> = Vec::new(); + let mut current = domain; + for _ in 0..=binders { + let (name, info, ty, body) = match self.b.node(current).clone() { + CoreNode::Forall { n, b, t, v } => (n, b, t, v), + CoreNode::BVar { .. } + | CoreNode::Sort { .. } + | CoreNode::Const { .. } + | CoreNode::App { .. } + | CoreNode::Lambda { .. } + | CoreNode::Let { .. } + | CoreNode::Mdata { .. } + | CoreNode::Projection { .. } + | CoreNode::Nat { .. } + | CoreNode::String { .. } => { + return Err(format!( + "the statement {} quantifies fewer than {binders} variables and a hypothesis", + self.b.show(domain) + )); + } + }; + let free = self.b.free(); + current = self.b.instantiate_beta(body, free); + opened.push((name, info, ty, free)); + } + let (hypothesis_name, hypothesis_info, hypothesis, hypothesis_free) = + opened.pop().ok_or("a clause without a hypothesis")?; + let frees: Vec = opened.iter().map(|(_, _, _, free)| *free).collect(); + let (a_ty, scrutinee, case, hypothesis_levels) = self.equation(hypothesis)?; + let (result_ty, _, _, conclusion_levels) = self.equation(current)?; + let at_scrutinee = self.form_term(form, &frees, scrutinee)?; + let variable = self.b.free(); + let transport = self.b.free(); + let at_variable = self.form_term(form, &frees, variable)?; + let eq_head = self.b.konst_at("Eq", conclusion_levels.clone()); + let motive_body = self + .b + .apps(eq_head, &[result_ty, at_scrutinee, at_variable]); + let hyp_head = self.b.konst_at("Eq", hypothesis_levels.clone()); + let hyp_at = self.b.apps(hyp_head, &[a_ty, scrutinee, variable]); + let inner = self + .b + .lam_over("_", EXPLICIT, hyp_at, transport, motive_body); + let motive = self.b.lam_over("c", EXPLICIT, a_ty, variable, inner); + let refl = self.b.konst_at("Eq.refl", conclusion_levels); + let refl = self.b.apps(refl, &[result_ty, at_scrutinee]); + let sort = hypothesis_levels + .first() + .cloned() + .ok_or("an equation without a universe")?; + let rec = self.b.konst_at("Eq.rec", vec![CoreLevel::Zero, sort]); + let mut term = self + .b + .apps(rec, &[a_ty, scrutinee, motive, refl, case, hypothesis_free]); + term = self.b.lam_over( + &hypothesis_name, + hypothesis_info, + hypothesis, + hypothesis_free, + term, + ); + for (name, info, ty, free) in opened.into_iter().rev() { + term = self.b.lam_over(&name, info, ty, free, term); + } + Ok(term) + } + + /// The type, sides, and universe of an equation. + fn equation(&self, equation: Term) -> Result<(Term, Term, Term, Vec), String> { + let (head, parts) = self.b.spine(equation); + match self.b.node(head) { + CoreNode::Const { n, u } if n == "Eq" && parts.len() == 3 => { + Ok((parts[0], parts[1], parts[2], u.clone())) + } + CoreNode::BVar { .. } + | CoreNode::Sort { .. } + | CoreNode::Const { .. } + | CoreNode::App { .. } + | CoreNode::Lambda { .. } + | CoreNode::Forall { .. } + | CoreNode::Let { .. } + | CoreNode::Mdata { .. } + | CoreNode::Projection { .. } + | CoreNode::Nat { .. } + | CoreNode::String { .. } => { + Err(format!("{} is not an equation", self.b.show(equation))) + } + } + } + + // --- The abstracted bodies ------------------------------------------- + + fn cons(&mut self, element: Term, head: Term, tail: Term) -> Term { + let cons = self.b.konst0("List.cons"); + self.b.apps(cons, &[element, head, tail]) + } + + /// `fun (name : ty) => body` over the free variable `free`. + fn lam1(&mut self, name: &str, ty: Term, free: Term, body: Term) -> Term { + self.b.lam_over(name, EXPLICIT, ty, free, body) + } + + /// `fun (_ : Unit) => value`. + fn thunk(&mut self, value: Term) -> Term { + let unit = self.b.konst("Unit"); + self.b.lam("_", EXPLICIT, unit, value) + } + + /// The eliminator of `Ordering` at the constant motive `result`. + fn ordering_cases(&mut self, result: Term, scrutinee: Term, arms: [Term; 3]) -> Term { + let ordering = self.b.konst("Ordering"); + let lifted = self.b.lift(result, 1, 0); + let motive = self.b.lam("_", EXPLICIT, ordering, lifted); + let head = self.b.konst_at("Ordering.casesOn", vec![one()]); + self.b + .apps(head, &[motive, scrutinee, arms[0], arms[1], arms[2]]) + } + + /// The eliminator of `List element` at the constant motive `result`. + fn list_cases( + &mut self, + element: Term, + result: Term, + scrutinee: Term, + nil: Term, + cons: Term, + ) -> Term { + let list = self.b.list_ty(element); + let lifted = self.b.lift(result, 1, 0); + let motive = self.b.lam("_", EXPLICIT, list, lifted); + let head = self + .b + .konst_at("List.casesOn", vec![one(), CoreLevel::Zero]); + self.b.apps(head, &[element, motive, scrutinee, nil, cons]) + } + + /// The eliminator of `Option value` at the constant motive `result`. + fn option_cases( + &mut self, + value: Term, + result: Term, + scrutinee: Term, + none: Term, + some: Term, + ) -> Term { + let option = self.b.konst0("Option"); + let option = self.b.app(option, value); + let lifted = self.b.lift(result, 1, 0); + let motive = self.b.lam("_", EXPLICIT, option, lifted); + let head = self + .b + .konst_at("Option.casesOn", vec![one(), CoreLevel::Zero]); + self.b.apps(head, &[value, motive, scrutinee, none, some]) + } + + fn none(&mut self, value: Term) -> Term { + let none = self.b.konst0("Option.none"); + self.b.app(none, value) + } + + fn some(&mut self, value: Term, item: Term) -> Term { + let some = self.b.konst0("Option.some"); + self.b.apps(some, &[value, item]) + } + + fn projection_of(&mut self, name: &str, alpha: Term, beta: Term, pair: Term) -> Term { + let head = self.b.konst00(name); + self.b.apps(head, &[alpha, beta, pair]) + } + + /// `if remaining.isEmpty then some order.reverse else none`, as the + /// runtime's `topological` writes it. + fn topo_empty(&mut self, kappa: Term, remaining: Term, order: Term) -> Term { + let is_empty = self.b.konst0("List.isEmpty"); + let empty = self.b.apps(is_empty, &[kappa, remaining]); + let reverse = self.b.konst0("List.reverse"); + let reversed = self.b.apps(reverse, &[kappa, order]); + let list = self.b.list_ty(kappa); + let found = self.some(list, reversed); + let missing = self.none(list); + let result = self.option_of(list); + self.ite(result, empty, found, missing) + } + + fn option_of(&mut self, value: Term) -> Term { + let option = self.b.konst0("Option"); + self.b.app(option, value) + } + + /// The function `F` of a clause at the scrutinee `scrutinee`, over the + /// clause's quantified variables `frees`. + #[allow(clippy::too_many_lines)] + fn form_term(&mut self, form: Form, frees: &[Term], scrutinee: Term) -> Result { + let at = |position: usize| -> Result { + frees.get(position).copied().ok_or_else(|| { + format!( + "a clause quantifies {} variables, not {}", + frees.len(), + position + 1 + ) + }) + }; + match form { + Form::InsertEntry { + kappa, + nu, + function, + } => { + let (key, value, other, stored, rest) = (at(0)?, at(1)?, at(2)?, at(3)?, at(4)?); + let entry = self.b.pair_ty(kappa, nu); + let new = self.b.pair(kappa, nu, key, value); + let old = self.b.pair(kappa, nu, other, stored); + let tail = self.cons(entry, old, rest); + let before = self.cons(entry, new, tail); + let replaced = self.cons(entry, new, rest); + let recursive = self.b.apps(function, &[key, value, rest]); + let after = self.cons(entry, old, recursive); + let result = self.b.list_ty(entry); + Ok(self.ordering_cases(result, scrutinee, [before, replaced, after])) + } + Form::RemoveEntry { + kappa, + nu, + function, + } => { + let (key, other, stored, rest) = (at(0)?, at(1)?, at(2)?, at(3)?); + let entry = self.b.pair_ty(kappa, nu); + let old = self.b.pair(kappa, nu, other, stored); + let kept = self.cons(entry, old, rest); + let recursive = self.b.apps(function, &[key, rest]); + let after = self.cons(entry, old, recursive); + let result = self.b.list_ty(entry); + Ok(self.ordering_cases(result, scrutinee, [kept, rest, after])) + } + Form::LookupEntry { nu, function } => { + let (key, stored, rest) = (at(0)?, at(2)?, at(3)?); + let missing = self.none(nu); + let found = self.some(nu, stored); + let recursive = self.b.apps(function, &[key, rest]); + let result = self.option_of(nu); + Ok(self.ordering_cases(result, scrutinee, [missing, found, recursive])) + } + Form::InsertElement { kappa, function } => { + let (key, other, rest) = (at(0)?, at(1)?, at(2)?); + let tail = self.cons(kappa, other, rest); + let before = self.cons(kappa, key, tail); + let kept = self.cons(kappa, other, rest); + let recursive = self.b.apps(function, &[key, rest]); + let after = self.cons(kappa, other, recursive); + let result = self.b.list_ty(kappa); + Ok(self.ordering_cases(result, scrutinee, [before, kept, after])) + } + Form::RemoveElement { kappa, function } => { + let (key, other, rest) = (at(0)?, at(1)?, at(2)?); + let kept = self.cons(kappa, other, rest); + let recursive = self.b.apps(function, &[key, rest]); + let after = self.cons(kappa, other, recursive); + let result = self.b.list_ty(kappa); + Ok(self.ordering_cases(result, scrutinee, [kept, rest, after])) + } + Form::ContainsElement { function } => { + let (key, rest) = (at(0)?, at(2)?); + let no = self.fls(); + let yes = self.tru(); + let recursive = self.b.apps(function, &[key, rest]); + let result = self.b.konst("Bool"); + Ok(self.ordering_cases(result, scrutinee, [no, yes, recursive])) + } + Form::CompareCodes { matcher, function } => { + let (lefts, rights) = (at(1)?, at(3)?); + let recursive = self.b.apps(function, &[lefts, rights]); + let result = self.b.konst("Ordering"); + Ok(self.sparse_ordering(matcher, result, scrutinee, recursive, None)) + } + Form::PairKey { + matcher, + alpha, + beta, + second, + } => { + let (left, right) = (at(0)?, at(1)?); + let left_second = self.projection_of("Prod.snd", alpha, beta, left); + let right_second = self.projection_of("Prod.snd", alpha, beta, right); + let compared = self.b.apps(second, &[left_second, right_second]); + let result = self.b.konst("Ordering"); + Ok(self.sparse_ordering(matcher, result, scrutinee, compared, None)) + } + Form::LessThan { matcher } => { + let yes = self.tru(); + let no = self.fls(); + let result = self.b.konst("Bool"); + Ok(self.sparse_ordering(matcher, result, scrutinee, yes, Some(no))) + } + Form::Reach { + matcher, + kappa, + reach, + insert, + } => { + let (fuel, seen) = (at(0)?, at(2)?); + let list = self.b.list_ty(kappa); + let stay = self.thunk(seen); + let next = self.b.free(); + let folded = self.fold_insert(kappa, insert, seen, next); + let recursive = self.b.apps(reach, &[fuel, next, folded]); + let advance = self.lam1("next", list, next, recursive); + let motive = self.constant_motive(list, list); + Ok(self + .b + .apps(matcher, &[kappa, motive, scrutinee, stay, advance])) + } + Form::TopoStuck { + kappa, + topo, + remove, + } => { + let (fuel, head, tail, order) = (at(0)?, at(1)?, at(2)?, at(3)?); + let remaining = self.cons(kappa, head, tail); + Ok(self.topo_cases(kappa, topo, remove, scrutinee, fuel, remaining, order)) + } + Form::TopoNext { + kappa, + topo, + remove, + } => { + let (fuel, remaining, order) = (at(0)?, at(1)?, at(2)?); + Ok(self.topo_cases(kappa, topo, remove, scrutinee, fuel, remaining, order)) + } + Form::IterateUntil { sigma, iterate } => { + let (fuel, state) = (at(0)?, at(1)?); + let bool = self.b.konst("Bool"); + let result = self.b.pair_ty(sigma, bool); + let done = self.tru(); + let finished = self.b.pair(sigma, bool, state, done); + let next = self.b.free(); + let continued = self.b.apps(iterate, &[fuel, next]); + let continues = self.lam1("next", sigma, next, continued); + Ok(self.option_cases(sigma, result, scrutinee, finished, continues)) + } + } + } + + /// The matcher of an `Ordering` match with a sparse default: its first + /// alternative is the constructor the runtime names, the default is + /// `other => other` unless `default` gives a constant. + fn sparse_ordering( + &mut self, + matcher: Term, + result: Term, + scrutinee: Term, + first: Term, + default: Option, + ) -> Term { + let ordering = self.b.konst("Ordering"); + let motive = self.constant_motive(ordering, result); + let thunk = self.thunk(first); + let variable = self.b.free(); + let body = default.unwrap_or(variable); + let other = self.lam1("other", ordering, variable, body); + self.b.apps(matcher, &[motive, scrutinee, thunk, other]) + } + + /// `fun (_ : domain) => result`. + fn constant_motive(&mut self, domain: Term, result: Term) -> Term { + let lifted = self.b.lift(result, 1, 0); + self.b.lam("_", EXPLICIT, domain, lifted) + } + + /// `List.foldl (fun acc key => insert key acc) seen items`. + fn fold_insert(&mut self, kappa: Term, insert: Term, seen: Term, items: Term) -> Term { + let list = self.b.list_ty(kappa); + let acc = self.b.free(); + let key = self.b.free(); + let inserted = self.b.apps(insert, &[key, acc]); + let inner = self.lam1("key", kappa, key, inserted); + let step = self.lam1("acc", list, acc, inner); + let foldl = self.b.konst00("List.foldl"); + self.b.apps(foldl, &[list, kappa, step, seen, items]) + } + + /// The match of `topological` on the nodes ready in `remaining`. + #[allow(clippy::too_many_arguments)] + fn topo_cases( + &mut self, + kappa: Term, + topo: Term, + remove: Term, + scrutinee: Term, + fuel: Term, + remaining: Term, + order: Term, + ) -> Term { + let list = self.b.list_ty(kappa); + let result = self.option_of(list); + let empty = self.topo_empty(kappa, remaining, order); + let ready = self.b.free(); + let tail = self.b.free(); + let rest = self.b.apps(remove, &[ready, remaining]); + let extended = self.cons(kappa, ready, order); + let call = self.b.apps(topo, &[fuel, rest, extended]); + let inner = self.lam1("tail", list, tail, call); + let step = self.lam1("ready", kappa, ready, inner); + self.list_cases(kappa, result, scrutinee, empty, step) + } +} diff --git a/crates/lexlean/src/production/certificate/data_a/src.rs b/crates/lexlean/src/production/certificate/data_a/src.rs index ac2d0837e..d8dbe31cb 100644 --- a/crates/lexlean/src/production/certificate/data_a/src.rs +++ b/crates/lexlean/src/production/certificate/data_a/src.rs @@ -367,9 +367,7 @@ impl Writer<'_, '_> { | SemanticPrimitive::GraphReachable | SemanticPrimitive::GraphTopological | SemanticPrimitive::LessThan => { - return Err(format!( - "{operation:?} is a collection operation, certified through its template" - )); + return self.collection_call(site, operation, types, args); } } // `quotient` and `remainder` take their zero case as a third operand. diff --git a/crates/lexlean/src/production/certificate/data_a/templates.rs b/crates/lexlean/src/production/certificate/data_a/templates.rs index e70fcce1f..b371ac0e1 100644 --- a/crates/lexlean/src/production/certificate/data_a/templates.rs +++ b/crates/lexlean/src/production/certificate/data_a/templates.rs @@ -1,22 +1,684 @@ //! Collection templates and key comparisons (SPEC.md §17.17.3). +//! +//! A collection primitive is lowered to a call of a library template's entry +//! function. Its relation is the template's lemma, applied at the call's +//! runtime functions: the lemma takes, as hypotheses, what each runtime +//! function does on an empty list and on each outcome of a key comparison +//! (`clauses.rs` proves them), the encoders of the call's types, and the +//! placement of the template's functions in the program. +use super::super::{element_of, instance_type}; +use super::calls::FunctionArgument; +use super::clauses::{Arg, Form}; use super::{Pf, Scope, Writer}; -use crate::calculus::Expr; -use crate::ir::semantic::{SemanticTerm, SemanticType}; +use crate::calculus::library::Template; +use crate::calculus::{Expr, Ty}; +use crate::ir::semantic::{SemanticPrimitive, SemanticTerm, SemanticType}; +use crate::production::kernel::{Given, Term}; +use crate::production::source::Site; + +/// What a template call is made of: where it is read, the template's entry +/// function in the program, the call's types, and its operands. +struct Call { + site: Site, + entry: Term, + tys: Vec, + instance: Vec, + types: Vec, + values: Vec, + functions: Vec, +} + +impl Call { + fn value(&self, index: usize, template: Template) -> Result { + self.values + .get(index) + .copied() + .ok_or_else(|| format!("template {} lacks operand {index}", template.name())) + } + + fn operand_type(&self, index: usize, template: Template) -> Result<&SemanticType, String> { + self.types + .get(index) + .ok_or_else(|| format!("template {} lacks operand {index}", template.name())) + } + + fn step(&self, template: Template) -> Result<&FunctionArgument, String> { + self.functions + .first() + .ok_or_else(|| format!("template {} lacks its step", template.name())) + } + + fn at(&self, index: usize) -> Result { + instance_type(&self.instance, index) + } +} impl<'a> Writer<'_, 'a> { /// A template call. pub(super) fn template_call( &mut self, - template: crate::calculus::library::Template, + template: Template, instance: &[SemanticType], arguments: &[SemanticTerm], types: &[SemanticType], expr: &'a Expr, scope: &Scope, ) -> Result { - let _ = (template, instance, arguments, types, expr, scope); - Err("collection templates".to_owned()) + let tys = instance + .iter() + .map(|ty| self.gen.target_ty(ty)) + .collect::, _>>()?; + let entry = *self + .gen + .templates + .get(&(template.name().to_owned(), tys.clone())) + .ok_or_else(|| format!("template {} has no instance in the layout", template.name()))?; + let Expr::Call { function, operands } = expr else { + return Err("the lowering of a template call is not a call".to_owned()); + }; + let (function, operand_exprs) = (*function, operands); + if function != entry || operand_exprs.len() != arguments.len() { + return Err(format!( + "a call of template {} at function {entry} lowered to a call of {function} with {} operands", + template.name(), + operand_exprs.len() + )); + } + let mut values = Vec::new(); + let mut proofs = Vec::new(); + let mut functions = Vec::new(); + for (argument, child) in arguments.iter().zip(operand_exprs) { + match self.function_argument(argument, child, scope)? { + Some(companion) => { + values.push(companion.value); + proofs.push(companion.proof); + functions.push(companion); + } + None => { + values.push(self.src(argument, scope)?); + proofs.push(self.prove(argument, child, scope)?); + } + } + } + let entry_term = self.nat_u(entry); + let call = Call { + site: scope.ctx.site.clone(), + entry: entry_term, + tys, + instance: instance.to_vec(), + types: types.to_vec(), + values, + functions, + }; + let lemma = self.template_lemma(template, &call)?; + let list = self.operands(scope.env, &proofs)?; + let (p, env) = (self.prog, scope.env); + self.apply( + "conv_call", + &[("p", p), ("env", env)], + &[ + Given::Proof(list.proof, list.ty), + Given::Proof(lemma.proof, lemma.ty), + ], + ) + } + + /// The hints every template lemma takes: the program, the entry function, + /// and the target types the entry is instantiated at, under the names the + /// lemma gives them. + fn call_hints(&mut self, call: &Call, names: &[&'static str]) -> Vec<(&'static str, Term)> { + let mut hints = vec![("p", self.prog), ("fi", call.entry)]; + for (name, ty) in names.iter().zip(&call.tys) { + let term = self.ty_term(ty); + hints.push((name, term)); + } + hints + } + + /// The template's lemma at the call. + #[allow(clippy::too_many_lines)] + fn template_lemma(&mut self, template: Template, call: &Call) -> Result { + match template { + Template::MapInsert => { + let (key, stored) = (call.at(0)?, call.at(1)?); + let (kappa, nu) = (self.lean_ty(&key)?, self.lean_ty(&stored)?); + let spec = self.key_spec(&call.site, &key)?; + let instance = self.key_instance(&call.site, &key)?; + let function = + self.collections_at(&call.site, "insertEntry", &[kappa, nu, instance])?; + let form = Form::InsertEntry { + kappa, + nu, + function, + }; + let (ev, list) = ( + self.enc(&stored)?, + self.list_bundle(call.operand_type(0, template)?)?, + ); + let mut hints = self.call_hints(call, &["tk", "tw"]); + hints.push(("ν", nu)); + let mut args = vec![ + Arg::proof(spec), + Arg::term(ev), + Arg::term(list), + Arg::refl(), + Arg::term(function), + Arg::refl(), + Arg::Clause(5, form), + Arg::Clause(5, form), + Arg::Clause(5, form), + Arg::refl(), + ]; + args.extend(self.operand_args(call, template, 0..3)?); + self.lemma_args("tpl_mapInsert", &hints, &args) + } + Template::MapRemove => { + let (key, stored) = (call.at(0)?, call.at(1)?); + let (kappa, nu) = (self.lean_ty(&key)?, self.lean_ty(&stored)?); + let spec = self.key_spec(&call.site, &key)?; + let instance = self.key_instance(&call.site, &key)?; + let function = + self.collections_at(&call.site, "removeEntry", &[kappa, nu, instance])?; + let form = Form::RemoveEntry { + kappa, + nu, + function, + }; + let (ev, list) = ( + self.enc(&stored)?, + self.list_bundle(call.operand_type(0, template)?)?, + ); + let mut hints = self.call_hints(call, &["tk", "tw"]); + hints.push(("ν", nu)); + let mut args = vec![ + Arg::proof(spec), + Arg::term(ev), + Arg::term(list), + Arg::refl(), + Arg::term(function), + Arg::refl(), + Arg::Clause(4, form), + Arg::Clause(4, form), + Arg::Clause(4, form), + Arg::refl(), + ]; + args.extend(self.operand_args(call, template, 0..2)?); + self.lemma_args("tpl_mapRemove", &hints, &args) + } + Template::MapLookup => { + let (key, stored) = (call.at(0)?, call.at(1)?); + let (kappa, nu) = (self.lean_ty(&key)?, self.lean_ty(&stored)?); + let spec = self.key_spec(&call.site, &key)?; + let instance = self.key_instance(&call.site, &key)?; + let function = + self.collections_at(&call.site, "lookupEntry", &[kappa, nu, instance])?; + let form = Form::LookupEntry { nu, function }; + let ev = self.enc(&stored)?; + let list = self.list_bundle(call.operand_type(0, template)?)?; + let options = self.option_bundle(&SemanticType::Option { + value: Box::new(stored.clone()), + })?; + let mut hints = self.call_hints(call, &["tk", "tw"]); + hints.push(("ν", nu)); + let mut args = vec![ + Arg::proof(spec), + Arg::term(ev), + Arg::term(list), + Arg::refl(), + Arg::term(options), + Arg::refl(), + Arg::term(function), + Arg::refl(), + Arg::Clause(4, form), + Arg::Clause(4, form), + Arg::Clause(4, form), + Arg::refl(), + ]; + args.extend(self.operand_args(call, template, 0..2)?); + self.lemma_args("tpl_mapLookup", &hints, &args) + } + Template::MapContains => { + let (key, stored) = (call.at(0)?, call.at(1)?); + let (kappa, nu) = (self.lean_ty(&key)?, self.lean_ty(&stored)?); + let spec = self.key_spec(&call.site, &key)?; + let instance = self.key_instance(&call.site, &key)?; + let function = + self.collections_at(&call.site, "lookupEntry", &[kappa, nu, instance])?; + let form = Form::LookupEntry { nu, function }; + let ev = self.enc(&stored)?; + let list = self.list_bundle(call.operand_type(0, template)?)?; + let mut hints = self.call_hints(call, &["tk", "tw"]); + hints.push(("ν", nu)); + let mut args = vec![ + Arg::proof(spec), + Arg::term(ev), + Arg::term(list), + Arg::refl(), + Arg::term(function), + Arg::refl(), + Arg::Clause(4, form), + Arg::Clause(4, form), + Arg::Clause(4, form), + ]; + args.extend(Arg::refls(2)); + args.extend(self.operand_args(call, template, 0..2)?); + self.lemma_args("tpl_mapContains", &hints, &args) + } + Template::MapKeys | Template::MapValues => { + let (key, stored) = (call.at(0)?, call.at(1)?); + let (lemma, projected) = match template { + Template::MapKeys => ("tpl_mapKeys", key.clone()), + Template::MapValues => ("tpl_mapValues", stored.clone()), + Template::MapInsert + | Template::MapRemove + | Template::MapLookup + | Template::MapContains + | Template::MapFold + | Template::SetInsert + | Template::SetRemove + | Template::SetContains + | Template::SetUnion + | Template::SetIntersection + | Template::SetDifference + | Template::SetFold + | Template::ListFold + | Template::Iterate + | Template::IterateUntil + | Template::GraphSuccessors + | Template::GraphReachable + | Template::GraphTopological => { + return Err("a projection of a non-projection template".to_owned()); + } + }; + let (kappa, nu) = (self.lean_ty(&key)?, self.lean_ty(&stored)?); + let (ek, ev) = (self.enc(&key)?, self.enc(&stored)?); + let list = self.list_bundle(call.operand_type(0, template)?)?; + let projections = self.list_bundle(&SemanticType::List { + element: Box::new(projected), + })?; + let mut hints = self.call_hints(call, &["tk", "tw"]); + hints.extend([("κ", kappa), ("ν", nu), ("ek", ek), ("ev", ev)]); + let mut args = vec![ + Arg::term(list), + Arg::refl(), + Arg::term(projections), + Arg::refl(), + Arg::refl(), + ]; + args.extend(self.operand_args(call, template, 0..1)?); + self.lemma_args(lemma, &hints, &args) + } + Template::MapFold => { + let (key, stored, state) = (call.at(0)?, call.at(1)?, call.at(2)?); + let step = *call.step(template)?; + let list = self.list_bundle(call.operand_type(2, template)?)?; + let mut hints = self.call_hints(call, &["tk", "tw", "ts"]); + let types = [ + ("κ", self.lean_ty(&key)?), + ("ν", self.lean_ty(&stored)?), + ("σ", self.lean_ty(&state)?), + ("ek", self.enc(&key)?), + ("ev", self.enc(&stored)?), + ("es", self.enc(&state)?), + ]; + hints.extend(types); + let mut args = vec![Arg::term(list), Arg::refl()]; + args.extend(Self::step_args(step)); + args.push(Arg::refl()); + args.extend(self.operand_args(call, template, 1..3)?); + self.lemma_args("tpl_mapFold", &hints, &args) + } + Template::SetInsert | Template::SetRemove | Template::SetContains => { + let key = call.at(0)?; + let kappa = self.lean_ty(&key)?; + let instance = self.key_instance(&call.site, &key)?; + let (lemma, name, form): (&str, &str, fn(Term, Term) -> Form) = match template { + Template::SetInsert => ("tpl_setInsert", "insertElement", |kappa, function| { + Form::InsertElement { kappa, function } + }), + Template::SetRemove => ("tpl_setRemove", "removeElement", |kappa, function| { + Form::RemoveElement { kappa, function } + }), + Template::SetContains => { + ("tpl_setContains", "containsElement", |_, function| { + Form::ContainsElement { function } + }) + } + Template::MapInsert + | Template::MapRemove + | Template::MapLookup + | Template::MapContains + | Template::MapKeys + | Template::MapValues + | Template::MapFold + | Template::SetUnion + | Template::SetIntersection + | Template::SetDifference + | Template::SetFold + | Template::ListFold + | Template::Iterate + | Template::IterateUntil + | Template::GraphSuccessors + | Template::GraphReachable + | Template::GraphTopological => { + return Err("an element operation of a non-element template".to_owned()); + } + }; + let function = self.collections_at(&call.site, name, &[kappa, instance])?; + let form = form(kappa, function); + let spec = self.key_spec(&call.site, &key)?; + let list = self.list_bundle(call.operand_type(0, template)?)?; + let hints = self.call_hints(call, &["tk"]); + let mut args = vec![ + Arg::proof(spec), + Arg::term(list), + Arg::refl(), + Arg::term(function), + Arg::refl(), + Arg::Clause(3, form), + Arg::Clause(3, form), + Arg::Clause(3, form), + Arg::refl(), + ]; + args.extend(self.operand_args(call, template, 0..2)?); + self.lemma_args(lemma, &hints, &args) + } + Template::SetUnion => { + let key = call.at(0)?; + let kappa = self.lean_ty(&key)?; + let instance = self.key_instance(&call.site, &key)?; + let function = + self.collections_at(&call.site, "insertElement", &[kappa, instance])?; + let form = Form::InsertElement { kappa, function }; + let spec = self.key_spec(&call.site, &key)?; + let list = self.list_bundle(call.operand_type(0, template)?)?; + let hints = self.call_hints(call, &["tk"]); + let mut args = vec![ + Arg::proof(spec), + Arg::term(list), + Arg::refl(), + Arg::term(function), + Arg::refl(), + Arg::Clause(3, form), + Arg::Clause(3, form), + Arg::Clause(3, form), + ]; + args.extend(Arg::refls(2)); + // The lemma folds the right operand into the left. + args.push(Arg::term(call.value(1, template)?)); + args.push(Arg::term(call.value(0, template)?)); + self.lemma_args("tpl_setUnion", &hints, &args) + } + Template::SetIntersection | Template::SetDifference => { + let key = call.at(0)?; + let kappa = self.lean_ty(&key)?; + let instance = self.key_instance(&call.site, &key)?; + let function = + self.collections_at(&call.site, "containsElement", &[kappa, instance])?; + let form = Form::ContainsElement { function }; + let lemma = match template { + Template::SetIntersection => "tpl_setIntersection", + Template::SetDifference => "tpl_setDifference", + Template::MapInsert + | Template::MapRemove + | Template::MapLookup + | Template::MapContains + | Template::MapKeys + | Template::MapValues + | Template::MapFold + | Template::SetInsert + | Template::SetRemove + | Template::SetContains + | Template::SetUnion + | Template::SetFold + | Template::ListFold + | Template::Iterate + | Template::IterateUntil + | Template::GraphSuccessors + | Template::GraphReachable + | Template::GraphTopological => { + return Err("a filter of a non-filter template".to_owned()); + } + }; + let spec = self.key_spec(&call.site, &key)?; + let list = self.list_bundle(call.operand_type(0, template)?)?; + let hints = self.call_hints(call, &["tk"]); + let mut args = vec![ + Arg::proof(spec), + Arg::term(list), + Arg::refl(), + Arg::term(function), + Arg::refl(), + Arg::Clause(3, form), + Arg::Clause(3, form), + Arg::Clause(3, form), + ]; + args.extend(Arg::refls(2)); + args.extend(self.operand_args(call, template, 0..2)?); + self.lemma_args(lemma, &hints, &args) + } + Template::SetFold | Template::ListFold => { + let (element, state) = (call.at(0)?, call.at(1)?); + let step = *call.step(template)?; + let list = self.list_bundle(call.operand_type(2, template)?)?; + let mut hints = self.call_hints(call, &["ta", "ts"]); + let types = [ + ("α", self.lean_ty(&element)?), + ("σ", self.lean_ty(&state)?), + ("ea", self.enc(&element)?), + ("es", self.enc(&state)?), + ]; + hints.extend(types); + let mut args = vec![Arg::term(list), Arg::refl()]; + args.extend(Self::step_args(step)); + args.push(Arg::refl()); + args.extend(self.operand_args(call, template, 1..3)?); + self.lemma_args("tpl_listFold", &hints, &args) + } + Template::Iterate => { + let state = call.at(0)?; + let step = *call.step(template)?; + let sigma = self.lean_ty(&state)?; + let iterate = + self.collections_at(&call.site, "iterate", &[sigma, call.value(0, template)?])?; + let mut hints = self.call_hints(call, &["ts"]); + hints.extend([("σ", sigma), ("es", self.enc(&state)?)]); + let mut args = Self::step_args(step); + args.push(Arg::term(iterate)); + args.extend(Arg::refls(2)); + args.push(Arg::refl()); + args.extend(self.operand_args(call, template, 1..3)?); + self.lemma_args("tpl_iterate", &hints, &args) + } + Template::IterateUntil => { + let state = call.at(0)?; + let step = *call.step(template)?; + let sigma = self.lean_ty(&state)?; + let iterate = self.collections_at( + &call.site, + "iterateUntil", + &[sigma, call.value(0, template)?], + )?; + let options = self.option_bundle(&SemanticType::Option { + value: Box::new(state.clone()), + })?; + let pairs = self.enc(&SemanticType::Product { + left: Box::new(state.clone()), + right: Box::new(SemanticType::Bool), + })?; + let form = Form::IterateUntil { sigma, iterate }; + let mut hints = self.call_hints(call, &["ts"]); + hints.extend([("σ", sigma), ("es", self.enc(&state)?)]); + let mut args = vec![ + Arg::term(options), + Arg::refl(), + Arg::term(pairs), + Arg::refl(), + ]; + args.extend(Self::step_args(step)); + args.extend([ + Arg::term(iterate), + Arg::refl(), + Arg::Clause(2, form), + Arg::Clause(3, form), + Arg::refl(), + ]); + args.extend(self.operand_args(call, template, 1..3)?); + self.lemma_args("tpl_iterateUntil", &hints, &args) + } + Template::GraphSuccessors => { + let node = call.at(0)?; + let kappa = self.lean_ty(&node)?; + let successors_ty = SemanticType::List { + element: Box::new(node.clone()), + }; + let nodes = self.list_bundle(&successors_ty)?; + let graph = self.list_bundle(call.operand_type(0, template)?)?; + let instance = self.key_instance(&call.site, &node)?; + let list = self.b.list_ty(kappa); + let look = + self.collections_at(&call.site, "lookupEntry", &[kappa, list, instance])?; + let form = Form::LookupEntry { + nu: list, + function: look, + }; + let spec = self.key_spec(&call.site, &node)?; + let hints = self.call_hints(call, &["tk"]); + let mut args = vec![ + Arg::proof(spec), + Arg::term(nodes), + Arg::term(graph), + Arg::refl(), + Arg::term(look), + Arg::refl(), + Arg::Clause(4, form), + Arg::Clause(4, form), + Arg::Clause(4, form), + ]; + args.extend(Arg::refls(2)); + args.extend(self.operand_args(call, template, 0..2)?); + self.lemma_args("tpl_graphSuccessors", &hints, &args) + } + Template::GraphReachable | Template::GraphTopological => { + let node = call.at(0)?; + let kappa = self.lean_ty(&node)?; + let nodes_ty = SemanticType::List { + element: Box::new(node.clone()), + }; + let graph = call.value(0, template)?; + let instance = self.key_instance(&call.site, &node)?; + let spec = self.key_spec(&call.site, &node)?; + let nodes = self.list_bundle(&nodes_ty)?; + let graph_bundle = self.list_bundle(call.operand_type(0, template)?)?; + let successors = + self.collections_at(&call.site, "graphSuccessors", &[kappa, instance, graph])?; + let sets = self.set_ops(&call.site, &node)?; + let lookups = self.look_ops(&call.site, &node, &nodes_ty)?; + let hints = self.call_hints(call, &["tk"]); + let mut args = vec![ + Arg::proof(spec), + Arg::term(nodes), + Arg::refl(), + Arg::term(graph_bundle), + Arg::refl(), + ]; + match template { + Template::GraphReachable => { + let insert = + self.collections_at(&call.site, "insertElement", &[kappa, instance])?; + let reach = self.collections_at( + &call.site, + "reachableFrom", + &[kappa, instance, graph], + )?; + let matcher = self.reach_matcher(&call.site)?; + let form = Form::Reach { + matcher, + kappa, + reach, + insert, + }; + args.extend([ + Arg::proof(sets), + Arg::proof(lookups), + Arg::term(graph), + Arg::term(successors), + Arg::refl(), + Arg::term(reach), + Arg::refl(), + Arg::Clause(3, form), + Arg::Clause(5, form), + ]); + args.extend(Arg::refls(13)); + args.push(Arg::term(call.value(1, template)?)); + self.lemma_args("tpl_graphReachable", &hints, &args) + } + Template::GraphTopological => { + let options = self.option_bundle(&SemanticType::Option { + value: Box::new(nodes_ty.clone()), + })?; + let topo = self.topo_ops(&call.site, &node, graph)?; + args.extend([ + Arg::term(options), + Arg::refl(), + Arg::proof(sets), + Arg::proof(lookups), + Arg::term(graph), + Arg::term(successors), + Arg::refl(), + Arg::proof(topo), + ]); + args.extend(Arg::refls(12)); + self.lemma_args("tpl_graphTopological", &hints, &args) + } + Template::MapInsert + | Template::MapRemove + | Template::MapLookup + | Template::MapContains + | Template::MapKeys + | Template::MapValues + | Template::MapFold + | Template::SetInsert + | Template::SetRemove + | Template::SetContains + | Template::SetUnion + | Template::SetIntersection + | Template::SetDifference + | Template::SetFold + | Template::ListFold + | Template::Iterate + | Template::IterateUntil + | Template::GraphSuccessors => { + Err("a traversal of a non-traversal template".to_owned()) + } + } + } + } + } + + /// The operands at `range` as the lemma's final explicit arguments. + fn operand_args( + &self, + call: &Call, + template: Template, + range: std::ops::Range, + ) -> Result, String> { + range + .map(|index| call.value(index, template).map(Arg::term)) + .collect() + } + + /// The step of a fold or iteration as the lemma takes it: the function, + /// its width predicate, closure index, captured values, and relation. + fn step_args(step: FunctionArgument) -> Vec { + vec![ + Arg::term(step.value), + Arg::term(step.fits), + Arg::term(step.index), + Arg::term(step.captures), + Arg::term(step.relation), + ] } /// A key comparison. @@ -27,7 +689,231 @@ impl<'a> Writer<'_, 'a> { expr: &'a Expr, scope: &Scope, ) -> Result { - let _ = (types, arguments, expr, scope); - Err("key comparisons".to_owned()) + let key = types.first().ok_or("a key comparison has no operand")?; + let site = scope.ctx.site.clone(); + // The lowering is a match on the realization's comparison of the + // operands (§17.14 `primitive.less_than`). + let Expr::Match { scrutinee, .. } = expr else { + return Err("the lowering of a key comparison is not a match".to_owned()); + }; + let Expr::Prim { operands, .. } = scrutinee.as_ref() else { + return Err("the lowering of a comparison does not compare".to_owned()); + }; + if operands.len() != arguments.len() { + return Err("a key comparison lowered to a comparison of other operands".to_owned()); + } + let operand_exprs = operands; + let mut proofs = Vec::new(); + for (argument, child) in arguments.iter().zip(operand_exprs) { + proofs.push(self.prove(argument, child, scope)?); + } + let list = self.operands(scope.env, &proofs)?; + let kappa = self.lean_ty(key)?; + let instance = self.key_instance(&site, key)?; + let less = self.collections_at(&site, "lessThan", &[kappa, instance])?; + let spec = self.key_spec(&site, key)?; + let matcher = self.less_matcher(&site)?; + let clause = Arg::Clause(2, Form::LessThan { matcher }); + // The operands appear in the lemma under the key encoder, which + // syntactic matching does not look through: they are given. + let [left, right] = arguments else { + return Err("a key comparison takes two operands".to_owned()); + }; + let (left, right) = (self.src(left, scope)?, self.src(right, scope)?); + let (p, env) = (self.prog, scope.env); + self.lemma_args( + "conv_lessThan", + &[("p", p), ("env", env), ("a", left), ("b", right)], + &[ + Arg::proof(spec), + Arg::term(less), + clause, + clause, + clause, + Arg::proof(list), + ], + ) + } + + /// A collection primitive's runtime call on operands of the given types, + /// as the module elaborates `(LexLeanCollections.op args : result)`: the + /// type arguments from the operands' types, and the `Key` instance of the + /// key type for an operation that orders. + pub(super) fn collection_call( + &mut self, + site: &Site, + operation: SemanticPrimitive, + types: &[SemanticType], + args: &[Term], + ) -> Result { + let operand = |index: usize| -> Result<&SemanticType, String> { + types + .get(index) + .ok_or_else(|| format!("{operation:?} lacks operand {index}")) + }; + let map = |ty: &SemanticType| -> Result<(SemanticType, SemanticType), String> { + match ty { + SemanticType::Map { key, value } => { + Ok((key.as_ref().clone(), value.as_ref().clone())) + } + SemanticType::Type + | SemanticType::Parameter { name: _ } + | SemanticType::Nat + | SemanticType::Bool + | SemanticType::Prop + | SemanticType::Unit + | SemanticType::Int + | SemanticType::Int8 + | SemanticType::Int16 + | SemanticType::Int32 + | SemanticType::Int64 + | SemanticType::UInt8 + | SemanticType::UInt16 + | SemanticType::UInt32 + | SemanticType::UInt64 + | SemanticType::String + | SemanticType::Bytes + | SemanticType::Ordering + | SemanticType::Option { value: _ } + | SemanticType::Result { ok: _, error: _ } + | SemanticType::List { element: _ } + | SemanticType::Named { + member: _, + arguments: _, + } + | SemanticType::Product { left: _, right: _ } + | SemanticType::Function { + parameters: _, + result: _, + } + | SemanticType::Set { element: _ } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure => Err(format!("{operation:?} of a non-map")), + } + }; + let (name, leading) = match operation { + SemanticPrimitive::MapInsert + | SemanticPrimitive::MapRemove + | SemanticPrimitive::MapLookup + | SemanticPrimitive::MapContains => { + let (key, value) = map(operand(0)?)?; + let instance = self.key_instance(site, &key)?; + ( + collection_name(operation)?, + vec![self.lean_ty(&key)?, self.lean_ty(&value)?, instance], + ) + } + SemanticPrimitive::MapSize + | SemanticPrimitive::MapKeys + | SemanticPrimitive::MapValues + | SemanticPrimitive::MapEntries => { + let (key, value) = map(operand(0)?)?; + ( + collection_name(operation)?, + vec![self.lean_ty(&key)?, self.lean_ty(&value)?], + ) + } + SemanticPrimitive::MapFold => { + let (key, value) = map(operand(2)?)?; + ( + collection_name(operation)?, + vec![ + self.lean_ty(&key)?, + self.lean_ty(&value)?, + self.lean_ty(operand(1)?)?, + ], + ) + } + SemanticPrimitive::SetInsert + | SemanticPrimitive::SetRemove + | SemanticPrimitive::SetContains + | SemanticPrimitive::SetUnion + | SemanticPrimitive::SetIntersection + | SemanticPrimitive::SetDifference => { + let element = element_of(operand(0)?)?; + let instance = self.key_instance(site, &element)?; + ( + collection_name(operation)?, + vec![self.lean_ty(&element)?, instance], + ) + } + SemanticPrimitive::SetSize | SemanticPrimitive::SetElements => { + let element = element_of(operand(0)?)?; + (collection_name(operation)?, vec![self.lean_ty(&element)?]) + } + SemanticPrimitive::SetFold | SemanticPrimitive::ListFold => { + let element = element_of(operand(2)?)?; + ( + collection_name(operation)?, + vec![self.lean_ty(&element)?, self.lean_ty(operand(1)?)?], + ) + } + SemanticPrimitive::Iterate | SemanticPrimitive::IterateUntil => ( + collection_name(operation)?, + vec![self.lean_ty(operand(2)?)?], + ), + SemanticPrimitive::GraphSuccessors + | SemanticPrimitive::GraphReachable + | SemanticPrimitive::GraphTopological => { + let (key, _) = map(operand(0)?)?; + let instance = self.key_instance(site, &key)?; + ( + collection_name(operation)?, + vec![self.lean_ty(&key)?, instance], + ) + } + SemanticPrimitive::LessThan => { + let key = operand(0)?; + let instance = self.key_instance(site, key)?; + ( + collection_name(operation)?, + vec![self.lean_ty(key)?, instance], + ) + } + SemanticPrimitive::Subtract + | SemanticPrimitive::Multiply + | SemanticPrimitive::Quotient + | SemanticPrimitive::Remainder + | SemanticPrimitive::Negate + | SemanticPrimitive::CheckedConvert + | SemanticPrimitive::CheckedAdd + | SemanticPrimitive::CheckedSubtract + | SemanticPrimitive::CheckedMultiply + | SemanticPrimitive::CheckedNegate + | SemanticPrimitive::CheckedQuotient + | SemanticPrimitive::BitAnd + | SemanticPrimitive::BitOr + | SemanticPrimitive::BitXor + | SemanticPrimitive::BitNot + | SemanticPrimitive::ShiftLeft + | SemanticPrimitive::ShiftRight + | SemanticPrimitive::Append + | SemanticPrimitive::Length + | SemanticPrimitive::Index + | SemanticPrimitive::Slice + | SemanticPrimitive::Utf8Encode + | SemanticPrimitive::Utf8Decode + | SemanticPrimitive::CompareBytes + | SemanticPrimitive::Equal + | SemanticPrimitive::SplitExact + | SemanticPrimitive::Join + | SemanticPrimitive::ParseDecimal + | SemanticPrimitive::FormatDecimal => { + return Err(format!("{operation:?} is not a collection operation")); + } + }; + let mut all = leading; + all.extend_from_slice(args); + self.collections_at(site, name, &all) + } +} + +/// The name of a collection primitive's runtime function. +fn collection_name(operation: SemanticPrimitive) -> Result<&'static str, String> { + let (namespace, name) = super::super::primitive_name(operation); + if namespace == "LexLeanCollections" { + Ok(name) + } else { + Err(format!("{operation:?} is not in the collections runtime")) } } From e07c1562cba2f81f528738d8a1db74797aecb63c Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 18:03:05 +0000 Subject: [PATCH 12/41] #89 stage 3: certificate A data: boundary validators and entry as kernel terms (scratch key shims) --- .../production/certificate/data_a/boundary.rs | 2461 ++++++++++++++++- 1 file changed, 2456 insertions(+), 5 deletions(-) diff --git a/crates/lexlean/src/production/certificate/data_a/boundary.rs b/crates/lexlean/src/production/certificate/data_a/boundary.rs index 88ec7ac2f..d07d7f468 100644 --- a/crates/lexlean/src/production/certificate/data_a/boundary.rs +++ b/crates/lexlean/src/production/certificate/data_a/boundary.rs @@ -1,21 +1,2472 @@ //! The boundary validators and the entry (SPEC.md §17.17). +//! +//! A root whose parameters carry §17.12's invariant has, besides its own +//! relation, one validator function per type that carries it. The module +//! states four things of each validator `k`: the Boolean function `__valid_k` +//! it computes, the proposition `__inv_k` it decides, the theorem `__viff_k` +//! that the one is the other, and the theorem `__vrel_k` that the program's +//! function `k` returns `__valid_k` of the encoding of its argument. A +//! container's four come from the preservation library's lemmas; a document +//! type's, and those of the containers nested in a recursive group of them, +//! are written by the recursor of the type, one motive per type of the group +//! and one minor premise per constructor, exactly as the group's encoders are. +//! The entry then states what the root returns on an input the validators +//! accept (`entry_accepts`) and on one they refuse (`entry_refuses`). +//! +//! Every equation the text proved by unfolding a definition or by `simp` is +//! here a definitional equality the kernel checks, or a rewriting by +//! `Eq.ndrec` along an equation the validators' decision theorems give. +use std::collections::{BTreeMap, BTreeSet}; + +use super::super::{checked, components, element_of, option_value, parts, Validation}; +use super::recplan::{PositionKind, RecCtor, RecPlan}; use super::rows::Sig; -use super::Writer; +use super::{Pf, RowKind, Writer, EXPLICIT}; +use crate::calculus::{Expr, Ty}; +use crate::ir::core::CoreLevel; use crate::ir::semantic::SemanticType; +use crate::production::kernel::{Given, Term}; +use crate::production::source::Site; -impl Writer<'_, '_> { +/// One validator of the root's boundary. +#[derive(Clone)] +struct Validator { + index: u64, + ty: SemanticType, + module: String, + kind: Validation, + /// The Lean type it validates. + lean: Term, + /// The encoder of that type. + enc: Term, +} + +/// The validators of the boundary by function index. +struct Boundary { + validators: BTreeMap, +} + +impl Boundary { + fn get(&self, index: u64) -> Result<&Validator, String> { + self.validators + .get(&index) + .ok_or_else(|| format!("function {index} is not a validator")) + } +} + +/// Which of a validator's four statements a recursor builds. +#[derive(Clone, Copy, PartialEq, Eq)] +enum Family { + /// `__valid_k`. + Valid, + /// `__inv_k`. + Inv, + /// `__viff_k`. + Viff, + /// `__vrel_k`. + Vrel, +} + +/// How a validator's recursive calls are written. +#[derive(Clone, Copy, PartialEq, Eq)] +enum Mode { + /// By the induction hypotheses of the recursor. + Ih, + /// By the implementation record of the group's other members. + Unsafe, +} + +/// A field of a constructor that its validator checks. +#[derive(Clone)] +struct Check { + field: usize, + validator: u64, +} + +/// One constructor of a validated type, as its validator reads it. +#[derive(Clone)] +struct CtorSpec { + fields: Vec, + /// Per field, the position of the group it recurses on. + recursive: Vec>, + /// The checked fields, in the order the validator tests them. + checks: Vec, + /// Whether the last check is the validator's result itself, else the + /// result after the last check is `true`. + direct: bool, + /// The arm of the validator's `match` that takes this constructor, if it + /// matches. + arm: Option, +} + +/// One validated type of a group. +#[derive(Clone)] +struct GroupPosition { + validator: Validator, + ctors: Vec, +} + +/// The validators defined together: the types of a recursive group, or one +/// document type. +struct Group { + positions: Vec, + /// The group's recursors; none for a document outside any recursion, + /// which is written by its eliminator. + plan: Option, +} + +/// A checked field met while deriving a validator's relation. +#[derive(Clone)] +struct Checked { + validator: Validator, + x: Term, + /// The proof of the validator's relation at `x`. + proof: Term, + /// `__valid_c x`. + valid: Term, +} + +/// The terms the variables of a validator's body stand for. +struct Binding { + vars: Vec<(u64, Term)>, + /// The encodings of a pair's components, for `first` and `second`. + pair: Option<(Term, Term)>, +} + +impl<'a> Writer<'_, 'a> { /// The validators and entry of a root with invariant-carrying parameters. pub(super) fn boundary( &mut self, sig: &Sig, parameters: &[SemanticType], ) -> Result<(), String> { - let _ = sig; self.gen.verify_boundary(parameters)?; - if self.gen.checks().is_empty() && self.gen.entry_of().is_none() { + let checks: Vec<(u64, SemanticType, String, Validation)> = self + .gen + .checks() + .into_iter() + .map(|(index, (ty, module, kind))| (index, ty.clone(), module.to_owned(), kind.clone())) + .collect(); + let entry = self + .gen + .entry_of() + .map(|(index, validators)| (index, validators.to_vec())); + if checks.is_empty() && entry.is_none() { return Ok(()); } - Err("boundary validators".to_owned()) + let adt_graph = self.gen.adt_graph()?; + let mut boundary = Boundary { + validators: BTreeMap::new(), + }; + for (index, ty, module, kind) in checks { + let lean = self.lean_ty(&ty)?; + let enc = self.enc(&ty)?; + boundary.validators.insert( + index, + Validator { + index, + ty, + module, + kind, + lean, + enc, + }, + ); + } + let graph: BTreeMap> = boundary + .validators + .iter() + .map(|(index, validator)| (*index, checked(&validator.kind).into_iter().collect())) + .collect(); + for component in components(&graph) { + let mut grouped = Vec::new(); + for index in &component { + let validator = boundary.get(*index)?; + grouped.push( + self.gen + .grouped(&validator.ty, &validator.kind, &adt_graph)?, + ); + } + let recursive = grouped.iter().any(|grouped| *grouped); + if (recursive && !grouped.iter().all(|grouped| *grouped)) + || (!recursive && component.len() > 1) + { + return Err(format!( + "the validators {component:?} call one another outside a recursive group" + )); + } + let mut members = component.clone(); + members.sort_unstable(); + if recursive { + self.recursive_validators(&boundary, &members)?; + } else { + self.plain_validator(&boundary, members[0])?; + } + } + match entry { + Some((index, validators)) => { + self.entry_section(&boundary, index, &validators, sig, parameters) + } + None => Ok(()), + } + } + + // --- Small terms ------------------------------------------------------ + + fn bool_ty(&mut self) -> Term { + self.b.konst("Bool") + } + + fn true_prop(&mut self) -> Term { + self.b.konst("True") + } + + fn iff(&mut self, a: Term, b: Term) -> Term { + let head = self.b.konst("Iff"); + self.b.apps(head, &[a, b]) + } + + fn and_prop(&mut self, a: Term, b: Term) -> Term { + let head = self.b.konst("And"); + self.b.apps(head, &[a, b]) + } + + /// `c = true`. + fn is_true(&mut self, c: Term) -> Term { + let bool = self.bool_ty(); + let tru = self.tru(); + self.eq(bool, c, tru) + } + + /// `Eq.refl a` at the type `ty : Type`. + fn refl(&mut self, ty: Term, a: Term) -> Term { + let head = self.b.konst1("Eq.refl"); + self.b.apps(head, &[ty, a]) + } + + fn valid_const(&mut self, index: u64) -> Term { + self.own(&format!("__valid_{index}")) + } + + fn inv_const(&mut self, index: u64) -> Term { + self.own(&format!("__inv_{index}")) + } + + fn viff_const(&mut self, index: u64) -> Term { + self.own(&format!("__viff_{index}")) + } + + fn vrel_const(&mut self, index: u64) -> Term { + self.own(&format!("__vrel_{index}")) + } + + /// `__valid_k x`. + fn valid_at(&mut self, index: u64, x: Term) -> Term { + let head = self.valid_const(index); + self.b.app(head, x) + } + + /// `__valid_k x = true ↔ __inv_k x`. + fn viff_claim(&mut self, index: u64, x: Term) -> Term { + let valid = self.valid_at(index, x); + let inv = self.inv_const(index); + let inv = self.b.app(inv, x); + let decided = self.is_true(valid); + self.iff(decided, inv) + } + + /// `FunRel __prog k [enc x] (Rel true (Value.bool (__valid_k x)))`. + fn vrel_claim(&mut self, validator: &Validator, x: Term) -> Term { + let valid = self.valid_at(validator.index, x); + let ctor = self.ts("Value.bool"); + let w = self.b.app(ctor, valid); + let tru = self.tru(); + let rel = self.rel(tru, w); + let value_ty = self.value_ty(); + let encoded = self.b.app(validator.enc, x); + let list = self.b.list(value_ty, &[encoded]); + let fun_rel = self.lp("FunRel"); + let index = self.nat_u(validator.index); + let prog = self.prog; + self.b.apps(fun_rel, &[prog, index, list, rel]) + } + + /// The statement a family makes of a validated type at `x`. + fn claim(&mut self, family: Family, position: &GroupPosition, x: Term) -> Term { + match family { + Family::Valid => self.bool_ty(), + Family::Inv => self.b.prop(), + Family::Viff => self.viff_claim(position.validator.index, x), + Family::Vrel => self.vrel_claim(&position.validator, x), + } + } + + /// The Lean type of a validated type's values and the row `__valid_k`'s + /// type. + fn row_type(&mut self, family: Family, validator: &Validator) -> Term { + match family { + Family::Valid => { + let bool = self.bool_ty(); + self.b.arrow(validator.lean, bool) + } + Family::Inv => { + let prop = self.b.prop(); + self.b.arrow(validator.lean, prop) + } + Family::Viff | Family::Vrel => { + let x = self.b.free(); + let claim = if family == Family::Viff { + self.viff_claim(validator.index, x) + } else { + self.vrel_claim(validator, x) + }; + self.b.pi_over("__v", EXPLICIT, validator.lean, x, claim) + } + } + } + + /// One row of a validator's four. + fn push_family( + &mut self, + family: Family, + validator: &Validator, + value: Term, + ) -> Result<(), String> { + let (name, kind) = match family { + Family::Valid => ( + format!("__valid_{}", validator.index), + RowKind::Definition { compiled: true }, + ), + Family::Inv => ( + format!("__inv_{}", validator.index), + RowKind::Definition { compiled: false }, + ), + Family::Viff => (format!("__viff_{}", validator.index), RowKind::Theorem), + Family::Vrel => (format!("__vrel_{}", validator.index), RowKind::Theorem), + }; + let ty = self.row_type(family, validator); + self.push_row(&name, kind, ty, value) + } + + // --- Validators outside any recursion ----------------------------------- + + /// The four rows of a validator whose type is not in a recursive group. + fn plain_validator(&mut self, boundary: &Boundary, index: u64) -> Result<(), String> { + let validator = boundary.get(index)?.clone(); + if let Validation::Document { fields } = &validator.kind { + let fields = fields.clone(); + return self.plain_document(boundary, &validator, &fields); + } + let [valid, inv, viff, vrel] = self.library_validator(boundary, &validator)?; + self.push_family(Family::Valid, &validator, valid)?; + self.push_family(Family::Inv, &validator, inv)?; + self.push_family(Family::Viff, &validator, viff)?; + self.push_family(Family::Vrel, &validator, vrel) + } + + /// The type of the validator's parameter in the program. + fn validator_parameter(&self, index: u64) -> Result<&'a Ty, String> { + let program = self.gen.program; + program + .functions + .get(index as usize) + .and_then(|function| function.types.first()) + .ok_or_else(|| format!("the validator {index} has no parameter")) + } + + /// The hints every template lemma of validator `index` takes alike. + fn template_hints(&mut self, index: u64) -> Vec<(&'static str, Term)> { + let fi = self.nat_u(index); + vec![("p", self.prog), ("fi", fi)] + } + + /// `(valid, inv, viff, vrel)` of a container or trivial validator, by the + /// library's definitions, propositions, decision theorems, and templates. + #[allow(clippy::too_many_lines)] + fn library_validator( + &mut self, + boundary: &Boundary, + validator: &Validator, + ) -> Result<[Term; 4], String> { + let site = Site { + module: validator.module.clone(), + substitution: BTreeMap::new(), + }; + let parameter = self.validator_parameter(validator.index)?; + let x = self.b.free(); + let mut hints = self.template_hints(validator.index); + let applied = match &validator.kind { + Validation::Trivial => { + let tru = self.tru(); + let valid = self.b.lam("_", EXPLICIT, validator.lean, tru); + let truth = self.true_prop(); + let inv = self.b.lam("_", EXPLICIT, validator.lean, truth); + let head = self.lp("validTrue_inv"); + let viff = self.b.app(head, validator.lean); + hints.push(("α", validator.lean)); + let t = self.ty_term(parameter); + hints.push(("t", t)); + let applied = self.apply( + "tpl_validTrue", + &hints, + &[Given::Term(validator.enc), Given::Refl, Given::Term(x)], + )?; + (valid, inv, viff, applied) + } + Validation::Set => { + let key = element_of(&validator.ty)?; + let kappa = self.lean_ty(&key)?; + let ek = self.enc(&key)?; + let cmp = self.key_order(&site, &key)?; + let spec = self.key_spec(&site, &key)?; + let bundle = self.list_bundle(&validator.ty)?; + let valid = { + let head = self.lp("ascSet"); + self.b.apps(head, &[kappa, cmp]) + }; + let inv = { + let head = self.lp("InvSet"); + self.b.apps(head, &[kappa, cmp]) + }; + let viff = { + let head = self.lp("ascSet_inv"); + self.b.apps(head, &[kappa, cmp]) + }; + let element = match parameter { + Ty::List { element } => element.as_ref(), + other => return Err(format!("a set validator over {other:?}")), + }; + let t = self.ty_term(element); + hints.extend([("κ", kappa), ("ek", ek), ("cmp", cmp), ("t", t)]); + let applied = self.apply( + "tpl_validSet", + &hints, + &[ + Given::Term(spec.proof), + Given::Term(bundle), + Given::Refl, + Given::Refl, + Given::Term(x), + ], + )?; + (valid, inv, viff, applied) + } + Validation::Map { value } => { + let (key, stored) = parts(&validator.ty)?; + let kappa = self.lean_ty(&key)?; + let nu = self.lean_ty(&stored)?; + let ek = self.enc(&key)?; + let ev = self.enc(&stored)?; + let cmp = self.key_order(&site, &key)?; + let spec = self.key_spec(&site, &key)?; + let bundle = self.list_bundle(&validator.ty)?; + let valid_v = self.valid_const(*value); + let inv_v = self.inv_const(*value); + let viff_v = self.viff_const(*value); + let vrel_v = self.vrel_const(*value); + let valid = { + let head = self.lp("ascMap"); + self.b.apps(head, &[kappa, nu, cmp, valid_v]) + }; + let inv = { + let head = self.lp("InvMap"); + self.b.apps(head, &[kappa, nu, cmp, inv_v]) + }; + let viff = { + let head = self.lp("ascMap_inv"); + self.b.apps(head, &[kappa, nu, cmp, valid_v, inv_v, viff_v]) + }; + let (tk, tw) = match parameter { + Ty::List { element } => match element.as_ref() { + Ty::Pair { left, right } => (left.as_ref(), right.as_ref()), + other => return Err(format!("a map validator over entries {other:?}")), + }, + other => return Err(format!("a map validator over {other:?}")), + }; + let (tk, tw) = (self.ty_term(tk), self.ty_term(tw)); + let fv = self.nat_u(*value); + hints.extend([ + ("κ", kappa), + ("ν", nu), + ("ek", ek), + ("ev", ev), + ("cmp", cmp), + ("fv", fv), + ("tk", tk), + ("tw", tw), + ]); + let applied = self.apply( + "tpl_validMap", + &hints, + &[ + Given::Term(spec.proof), + Given::Term(valid_v), + Given::Term(bundle), + Given::Refl, + Given::Term(vrel_v), + Given::Refl, + Given::Term(x), + ], + )?; + (valid, inv, viff, applied) + } + Validation::List { element } => { + let inner = element_of(&validator.ty)?; + let alpha = self.lean_ty(&inner)?; + let ea = self.enc(&inner)?; + let bundle = self.list_bundle(&validator.ty)?; + let valid_e = self.valid_const(*element); + let inv_e = self.inv_const(*element); + let viff_e = self.viff_const(*element); + let vrel_e = self.vrel_const(*element); + let valid = { + let head = self.lp("validList"); + self.b.apps(head, &[alpha, valid_e]) + }; + let inv = { + let head = self.lp("InvList"); + self.b.apps(head, &[alpha, inv_e]) + }; + let viff = { + let head = self.lp("validList_inv"); + self.b.apps(head, &[alpha, valid_e, inv_e, viff_e]) + }; + let t = match parameter { + Ty::List { element } => self.ty_term(element), + other => return Err(format!("a list validator over {other:?}")), + }; + let fv = self.nat_u(*element); + hints.extend([("α", alpha), ("ea", ea), ("fv", fv), ("t", t)]); + let applied = self.apply( + "tpl_validList", + &hints, + &[ + Given::Term(valid_e), + Given::Term(bundle), + Given::Refl, + Given::Term(vrel_e), + Given::Refl, + Given::Term(x), + ], + )?; + (valid, inv, viff, applied) + } + Validation::Option { value } => { + let inner = option_value(&validator.ty)?; + let alpha = self.lean_ty(&inner)?; + let ea = self.enc(&inner)?; + let valid_v = self.valid_const(*value); + let inv_v = self.inv_const(*value); + let viff_v = self.viff_const(*value); + let vrel_v = self.vrel_const(*value); + let valid = { + let head = self.lp("validOption"); + self.b.apps(head, &[alpha, valid_v]) + }; + let inv = { + let head = self.lp("InvOption"); + self.b.apps(head, &[alpha, inv_v]) + }; + let viff = { + let head = self.lp("validOption_inv"); + self.b.apps(head, &[alpha, valid_v, inv_v, viff_v]) + }; + let t = match parameter { + Ty::Option { value } => self.ty_term(value), + other => return Err(format!("an option validator over {other:?}")), + }; + let fv = self.nat_u(*value); + hints.extend([("α", alpha), ("ea", ea), ("fv", fv), ("t", t)]); + let applied = self.apply( + "tpl_validOption", + &hints, + &[ + Given::Term(valid_v), + Given::Term(vrel_v), + Given::Refl, + Given::Term(x), + ], + )?; + (valid, inv, viff, applied) + } + Validation::Pair { left, right } => { + let (a, b) = parts(&validator.ty)?; + let alpha = self.lean_ty(&a)?; + let beta = self.lean_ty(&b)?; + let ea = self.enc(&a)?; + let eb = self.enc(&b)?; + let (valid_a, valid_b) = (self.valid_const(*left), self.valid_const(*right)); + let (inv_a, inv_b) = (self.inv_const(*left), self.inv_const(*right)); + let (viff_a, viff_b) = (self.viff_const(*left), self.viff_const(*right)); + let (vrel_a, vrel_b) = (self.vrel_const(*left), self.vrel_const(*right)); + let valid = { + let head = self.lp("validPair"); + self.b.apps(head, &[alpha, beta, valid_a, valid_b]) + }; + let inv = { + let head = self.lp("InvPair"); + self.b.apps(head, &[alpha, beta, inv_a, inv_b]) + }; + let viff = { + let head = self.lp("validPair_inv"); + self.b.apps( + head, + &[alpha, beta, valid_a, valid_b, inv_a, inv_b, viff_a, viff_b], + ) + }; + let (ta, tb) = match parameter { + Ty::Pair { left, right } => (self.ty_term(left), self.ty_term(right)), + other => return Err(format!("a pair validator over {other:?}")), + }; + let (fa, fb) = (self.nat_u(*left), self.nat_u(*right)); + hints.extend([ + ("α", alpha), + ("β", beta), + ("ea", ea), + ("eb", eb), + ("fa", fa), + ("fb", fb), + ("ta", ta), + ("tb", tb), + ]); + let applied = self.apply( + "tpl_validPair", + &hints, + &[ + Given::Term(valid_a), + Given::Term(valid_b), + Given::Term(vrel_a), + Given::Term(vrel_b), + Given::Refl, + Given::Term(x), + ], + )?; + (valid, inv, viff, applied) + } + Validation::Result { ok, error } => { + let (ok_ty, error_ty) = parts(&validator.ty)?; + let alpha = self.lean_ty(&ok_ty)?; + let epsilon = self.lean_ty(&error_ty)?; + let ea = self.enc(&ok_ty)?; + let ee = self.enc(&error_ty)?; + let (valid_a, valid_e) = (self.valid_const(*ok), self.valid_const(*error)); + let (inv_a, inv_e) = (self.inv_const(*ok), self.inv_const(*error)); + let (viff_a, viff_e) = (self.viff_const(*ok), self.viff_const(*error)); + let (vrel_a, vrel_e) = (self.vrel_const(*ok), self.vrel_const(*error)); + let valid = { + let head = self.lp("validExcept"); + self.b.apps(head, &[epsilon, alpha, valid_e, valid_a]) + }; + let inv = { + let head = self.lp("InvExcept"); + self.b.apps(head, &[epsilon, alpha, inv_e, inv_a]) + }; + let viff = { + let head = self.lp("validExcept_inv"); + self.b.apps( + head, + &[epsilon, alpha, valid_e, valid_a, inv_e, inv_a, viff_e, viff_a], + ) + }; + let (ta, te) = match parameter { + Ty::Result { ok, error } => (self.ty_term(ok), self.ty_term(error)), + other => return Err(format!("a result validator over {other:?}")), + }; + let (fa, fe) = (self.nat_u(*ok), self.nat_u(*error)); + hints.extend([ + ("α", alpha), + ("ε", epsilon), + ("ea", ea), + ("ee", ee), + ("fa", fa), + ("fe", fe), + ("ta", ta), + ("te", te), + ]); + let applied = self.apply( + "tpl_validResult", + &hints, + &[ + Given::Term(valid_e), + Given::Term(valid_a), + Given::Term(vrel_a), + Given::Term(vrel_e), + Given::Refl, + Given::Term(x), + ], + )?; + (valid, inv, viff, applied) + } + Validation::Document { fields: _ } => { + return Err("a document validator is not a library validator".to_owned()); + } + }; + let (valid, inv, viff, relation) = applied; + let vrel = self + .b + .lam_over("__v", EXPLICIT, validator.lean, x, relation.proof); + let _ = boundary; + Ok([valid, inv, viff, vrel]) + } + + // --- Document types ----------------------------------------------------- + + /// The constructors of a document type outside any recursion. + fn plain_constructors(&self, ty: &SemanticType) -> Result, String> { + let shape = self.gen.source.document(ty)?; + Ok(shape + .fields + .iter() + .map(|fields| RecCtor { + fields: fields.clone(), + recursive: vec![None; fields.len()], + }) + .collect()) + } + + /// The constructors of a validated type as its validator reads them. + fn constructor_specs( + &self, + validator: &Validator, + constructors: &[RecCtor], + ) -> Result, String> { + let spec = |constructor: &RecCtor, + checks: Vec<(usize, u64)>, + direct: bool, + arm: Option| + -> Result { + for (field, _) in &checks { + if *field >= constructor.fields.len() { + return Err(format!( + "the validator {} checks a field its constructor lacks", + validator.index + )); + } + } + Ok(CtorSpec { + fields: constructor.fields.clone(), + recursive: constructor.recursive.clone(), + checks: checks + .into_iter() + .map(|(field, validator)| Check { field, validator }) + .collect(), + direct, + arm, + }) + }; + let wanted = |count: usize| -> Result<(), String> { + if constructors.len() == count { + Ok(()) + } else { + Err(format!( + "the validator {} reads {} constructors, the type has {}", + validator.index, + count, + constructors.len() + )) + } + }; + match &validator.kind { + Validation::Document { fields } => { + wanted(fields.len())?; + constructors + .iter() + .zip(fields) + .enumerate() + .map(|(position, (constructor, checks))| { + if checks.len() != constructor.fields.len() { + return Err(format!( + "the validator {} checks another number of fields", + validator.index + )); + } + let checks = checks + .iter() + .enumerate() + .filter_map(|(field, check)| check.map(|check| (field, check))) + .collect(); + spec(constructor, checks, false, Some(position)) + }) + .collect() + } + Validation::List { element } => { + wanted(2)?; + Ok(vec![ + spec(&constructors[0], Vec::new(), true, Some(0))?, + spec( + &constructors[1], + vec![(0, *element), (1, validator.index)], + true, + Some(1), + )?, + ]) + } + Validation::Option { value } => { + wanted(2)?; + Ok(vec![ + spec(&constructors[0], Vec::new(), true, Some(0))?, + spec(&constructors[1], vec![(0, *value)], true, Some(1))?, + ]) + } + Validation::Pair { left, right } => { + wanted(1)?; + Ok(vec![spec( + &constructors[0], + vec![(0, *left), (1, *right)], + true, + None, + )?]) + } + // The program matches the value arm first, the error arm second; + // the type's constructors are the other way round. + Validation::Result { ok, error } => { + wanted(2)?; + Ok(vec![ + spec(&constructors[0], vec![(0, *error)], true, Some(1))?, + spec(&constructors[1], vec![(0, *ok)], true, Some(0))?, + ]) + } + Validation::Trivial | Validation::Set | Validation::Map { value: _ } => Err(format!( + "the validator of `{}` cannot belong to a recursive group", + self.gen.source.type_text(&validator.ty) + )), + } + } + + /// A document type's validator outside any recursion: by its eliminator. + fn plain_document( + &mut self, + boundary: &Boundary, + validator: &Validator, + fields: &[Vec>], + ) -> Result<(), String> { + let _ = fields; + let constructors = self.plain_constructors(&validator.ty)?; + let ctors = self.constructor_specs(validator, &constructors)?; + let group = Group { + positions: vec![GroupPosition { + validator: validator.clone(), + ctors, + }], + plan: None, + }; + for family in [Family::Valid, Family::Inv, Family::Viff, Family::Vrel] { + let values = self.family_values(boundary, family, &group)?; + self.push_family(family, validator, values[0])?; + } + Ok(()) + } + + // --- Recursive groups --------------------------------------------------- + + /// The validators of a recursive group of document types and the + /// containers nested in them, which are mutually structural. + #[allow(clippy::too_many_lines)] + fn recursive_validators( + &mut self, + boundary: &Boundary, + members: &[u64], + ) -> Result<(), String> { + let document = members + .iter() + .find_map(|index| { + let validator = boundary.validators.get(index)?; + matches!(validator.kind, Validation::Document { fields: _ }) + .then(|| validator.ty.clone()) + }) + .ok_or("a recursive group of validators with no document type")?; + let plan = self.rec_plan(&document)?; + let mut positions = Vec::new(); + for entry in &plan.positions { + let wanted = match &entry.kind { + PositionKind::Aux(aux) => self.gen.source.type_text(&aux.original), + PositionKind::Block { index: _ } | PositionKind::Builtin => { + self.gen.source.type_text(&entry.ty) + } + }; + let validator = members + .iter() + .filter_map(|index| boundary.validators.get(index)) + .find(|validator| self.gen.source.type_text(&validator.ty) == wanted) + .ok_or_else(|| format!("`{wanted}` is in a recursive group but has no validator"))?; + positions.push(GroupPosition { + validator: validator.clone(), + ctors: self.constructor_specs(validator, &entry.constructors)?, + }); + } + for member in members { + if !positions + .iter() + .any(|position| position.validator.index == *member) + { + return Err(format!( + "the validator {member} of a recursive group is at no position of its recursors" + )); + } + } + // A recursive field is checked by the validator of the position it + // recurses on. + for position in &positions { + for ctor in &position.ctors { + for check in &ctor.checks { + if let Some(target) = ctor.recursive.get(check.field).copied().flatten() { + let expected = positions + .get(target) + .map(|position| position.validator.index); + if expected != Some(check.validator) { + return Err(format!( + "the validator {} checks a recursive field with the validator {}, not the one of its position", + position.validator.index, check.validator + )); + } + } + } + } + } + let group = Group { + positions, + plan: Some(plan), + }; + let position_of = |index: u64| { + group + .positions + .iter() + .position(|position| position.validator.index == index) + }; + let mut order: Vec<(u64, usize)> = Vec::new(); + for member in members { + order.push((*member, position_of(*member).ok_or("a member outside the group")?)); + } + let valid = self.family_values(boundary, Family::Valid, &group)?; + let mut names = Vec::new(); + for (member, position) in &order { + let validator = boundary.get(*member)?.clone(); + self.push_family(Family::Valid, &validator, valid[*position])?; + names.push(format!("__valid_{member}")); + } + self.group_rows(&names); + self.unsafe_records(boundary, &group, &order)?; + for family in [Family::Inv, Family::Viff, Family::Vrel] { + let values = self.family_values(boundary, family, &group)?; + for (member, position) in &order { + let validator = boundary.get(*member)?.clone(); + self.push_family(family, &validator, values[*position])?; + } + } + for (member, position) in &order { + let validator = boundary.get(*member)?.clone(); + self.inv_corollary(&group, &validator, *position)?; + } + Ok(()) + } + + /// The implementation records of a group's `__valid_k`: direct recursion + /// by the eliminator of each type, calling the records of the others. + fn unsafe_records( + &mut self, + boundary: &Boundary, + group: &Group, + order: &[(u64, usize)], + ) -> Result<(), String> { + let bool = self.bool_ty(); + let mut names = Vec::new(); + for (member, position) in order { + let validator = boundary.get(*member)?.clone(); + let free = self.b.free(); + let one = CoreLevel::Succ(Box::new(CoreLevel::Zero)); + let mut minors = Vec::new(); + for ctor in 0..group.positions[*position].ctors.len() { + minors.push(self.minor( + boundary, + Family::Valid, + group, + *position, + ctor, + Mode::Unsafe, + )?); + } + let motive = { + let lifted = self.b.lift(bool, 1, 0); + self.b.lam("_", EXPLICIT, validator.lean, lifted) + }; + let cases = self.cases_on(&validator.ty, free, motive, one, &minors)?; + let value = self + .b + .lam_over("__v", EXPLICIT, validator.lean, free, cases); + let ty = self.row_type(Family::Valid, &validator); + let name = format!("__valid_{member}._unsafe_rec"); + self.push_row(&name, RowKind::Partial, ty, value)?; + names.push(name); + } + self.group_rows(&names); + Ok(()) + } + + /// `__vinv_k`: the group's proposition of a nested container is the + /// library's proposition of the container over the group's. + fn inv_corollary( + &mut self, + group: &Group, + validator: &Validator, + position: usize, + ) -> Result<(), String> { + let x = self.b.free(); + let inv_k = self.inv_const(validator.index); + let inv_at = self.b.app(inv_k, x); + let (library, proof) = match &validator.kind { + Validation::List { element } => { + let inner = element_of(&validator.ty)?; + let alpha = self.lean_ty(&inner)?; + let inv_e = self.inv_const(*element); + let head = self.lp("InvList"); + let library = self.b.apps(head, &[alpha, inv_e, x]); + let nil = self.ctor_value(&group.positions[position], 0, &[])?; + let nil_inv = self.b.app(inv_k, nil); + let base = { + let head = self.b.konst("Iff.refl"); + self.b.app(head, nil_inv) + }; + let (head_x, tail_x) = (self.b.free(), self.b.free()); + let cons = self.ctor_value(&group.positions[position], 1, &[head_x, tail_x])?; + let cons_inv = self.b.app(inv_k, cons); + let step_body = { + let head = self.b.konst("Iff.refl"); + self.b.app(head, cons_inv) + }; + let element_ty = self.lean_ty(&inner)?; + let list_ty = validator.lean; + let step = self + .b + .lam_over("__x1", EXPLICIT, list_ty, tail_x, step_body); + let step = self + .b + .lam_over("__x0", EXPLICIT, element_ty, head_x, step); + let lemma = self.lp("invList_eqs"); + let proof = self + .b + .apps(lemma, &[alpha, inv_e, inv_k, base, step, x]); + (library, proof) + } + Validation::Option { value } => { + let inner = option_value(&validator.ty)?; + let alpha = self.lean_ty(&inner)?; + let inv_v = self.inv_const(*value); + let head = self.lp("InvOption"); + let library = self.b.apps(head, &[alpha, inv_v, x]); + let claim = self.iff(inv_at, library); + let motive = self.b.lam_over("__v", EXPLICIT, validator.lean, x, claim); + let proof = self.iff_reflexive_cases(group, validator, position, motive, x)?; + (library, proof) + } + Validation::Pair { left, right } => { + let (a, b) = parts(&validator.ty)?; + let alpha = self.lean_ty(&a)?; + let beta = self.lean_ty(&b)?; + let (inv_a, inv_b) = (self.inv_const(*left), self.inv_const(*right)); + let head = self.lp("InvPair"); + let library = self.b.apps(head, &[alpha, beta, inv_a, inv_b, x]); + let claim = self.iff(inv_at, library); + let motive = self.b.lam_over("__v", EXPLICIT, validator.lean, x, claim); + let proof = self.iff_reflexive_cases(group, validator, position, motive, x)?; + (library, proof) + } + Validation::Result { ok, error } => { + let (ok_ty, error_ty) = parts(&validator.ty)?; + let alpha = self.lean_ty(&ok_ty)?; + let epsilon = self.lean_ty(&error_ty)?; + let (inv_a, inv_e) = (self.inv_const(*ok), self.inv_const(*error)); + let head = self.lp("InvExcept"); + let library = self.b.apps(head, &[epsilon, alpha, inv_e, inv_a, x]); + let claim = self.iff(inv_at, library); + let motive = self.b.lam_over("__v", EXPLICIT, validator.lean, x, claim); + let proof = self.iff_reflexive_cases(group, validator, position, motive, x)?; + (library, proof) + } + Validation::Trivial + | Validation::Set + | Validation::Map { value: _ } + | Validation::Document { fields: _ } => return Ok(()), + }; + let claim = self.iff(inv_at, library); + let ty = self.b.pi_over("__v", EXPLICIT, validator.lean, x, claim); + let value = self + .b + .lam_over("__v", EXPLICIT, validator.lean, x, proof); + self.push_row( + &format!("__vinv_{}", validator.index), + RowKind::Theorem, + ty, + value, + ) + } + + /// `casesOn x` proving `motive x` by `Iff.refl` at each constructor: the + /// two propositions are the same after unfolding. + fn iff_reflexive_cases( + &mut self, + group: &Group, + validator: &Validator, + position: usize, + motive: Term, + x: Term, + ) -> Result { + let inv_k = self.inv_const(validator.index); + let mut minors = Vec::new(); + let constructors = group.positions[position].ctors.clone(); + for (ctor, spec) in constructors.iter().enumerate() { + let mut frees = Vec::new(); + let mut types = Vec::new(); + for field in &spec.fields { + frees.push(self.b.free()); + types.push(self.lean_ty(field)?); + } + let value = self.ctor_value(&group.positions[position], ctor, &frees)?; + let at = self.b.app(inv_k, value); + let head = self.b.konst("Iff.refl"); + let mut minor = self.b.app(head, at); + for (index, free) in frees.iter().enumerate().rev() { + minor = self.b.lam_over( + &format!("__x{index}"), + EXPLICIT, + types[index], + *free, + minor, + ); + } + minors.push(minor); + } + self.cases_on(&validator.ty, x, motive, CoreLevel::Zero, &minors) + } + + /// The constructor of a validated type applied to its fields. + fn ctor_value( + &mut self, + position: &GroupPosition, + ctor: usize, + fields: &[Term], + ) -> Result { + let ty = &position.validator.ty; + match &position.validator.kind { + Validation::Document { fields: _ } => { + let (member, arguments) = match ty { + SemanticType::Named { member, arguments } => (member, arguments), + other => return Err(format!("a document validator over {other:?}")), + }; + let shape = self.gen.source.document(ty)?; + let name = shape + .names + .get(ctor) + .ok_or_else(|| format!("the document has no constructor {ctor}"))?; + let owner = self.document_name(member)?; + let head = self.b.konst(&format!("{owner}.{name}")); + let mut all = Vec::new(); + for argument in arguments { + all.push(self.lean_ty(argument)?); + } + all.extend_from_slice(fields); + Ok(self.b.apps(head, &all)) + } + Validation::List { element: _ } => { + let alpha = self.lean_ty(&element_of(ty)?)?; + if ctor == 0 { + let head = self.b.konst0("List.nil"); + Ok(self.b.app(head, alpha)) + } else { + let head = self.b.konst0("List.cons"); + let mut all = vec![alpha]; + all.extend_from_slice(fields); + Ok(self.b.apps(head, &all)) + } + } + Validation::Option { value: _ } => { + let alpha = self.lean_ty(&option_value(ty)?)?; + let name = if ctor == 0 { + "Option.none" + } else { + "Option.some" + }; + let head = self.b.konst0(name); + let mut all = vec![alpha]; + all.extend_from_slice(fields); + Ok(self.b.apps(head, &all)) + } + Validation::Pair { left: _, right: _ } => { + let (a, b) = parts(ty)?; + let (alpha, beta) = (self.lean_ty(&a)?, self.lean_ty(&b)?); + match fields { + [first, second] => Ok(self.b.pair(alpha, beta, *first, *second)), + _ => Err("a pair of other than two components".to_owned()), + } + } + Validation::Result { ok: _, error: _ } => { + let (ok, error) = parts(ty)?; + let (alpha, epsilon) = (self.lean_ty(&ok)?, self.lean_ty(&error)?); + let name = if ctor == 0 { + "Except.error" + } else { + "Except.ok" + }; + let head = self.b.konst00(name); + let mut all = vec![epsilon, alpha]; + all.extend_from_slice(fields); + Ok(self.b.apps(head, &all)) + } + Validation::Trivial | Validation::Set | Validation::Map { value: _ } => Err( + "a validator without constructors".to_owned(), + ), + } + } + + // --- The four families -------------------------------------------------- + + /// For each position of the group, `fun v => …`: the statement of + /// `family` by the group's recursor (or eliminator) at that position. + fn family_values( + &mut self, + boundary: &Boundary, + family: Family, + group: &Group, + ) -> Result, String> { + let one = CoreLevel::Succ(Box::new(CoreLevel::Zero)); + let level = match family { + Family::Valid | Family::Inv => one, + Family::Viff | Family::Vrel => CoreLevel::Zero, + }; + let mut motives = Vec::new(); + for position in &group.positions { + let free = self.b.free(); + let claim = self.claim(family, position, free); + motives.push( + self.b + .lam_over("__v", EXPLICIT, position.validator.lean, free, claim), + ); + } + let mut minors: Vec> = Vec::new(); + for (index, position) in group.positions.iter().enumerate() { + let mut row = Vec::new(); + for ctor in 0..position.ctors.len() { + row.push(self.minor(boundary, family, group, index, ctor, Mode::Ih)?); + } + minors.push(row); + } + let mut values = Vec::new(); + match &group.plan { + Some(plan) => { + let mut params = Vec::new(); + for argument in &plan.params { + params.push(self.lean_ty(argument)?); + } + let all: Vec = minors.iter().flatten().copied().collect(); + for (index, position) in group.positions.iter().enumerate() { + let recursor = plan + .recursors + .get(index) + .ok_or("a position without a recursor")?; + let recursor = self.b.konst_at(recursor, plan.levels(level.clone())); + let free = self.b.free(); + let mut args = params.clone(); + args.extend(motives.iter().copied()); + args.extend(all.iter().copied()); + args.push(free); + let applied = self.b.apps(recursor, &args); + values.push(self.b.lam_over( + "__v", + EXPLICIT, + position.validator.lean, + free, + applied, + )); + } + } + None => { + for (index, position) in group.positions.iter().enumerate() { + let free = self.b.free(); + let cases = self.cases_on( + &position.validator.ty, + free, + motives[index], + level.clone(), + &minors[index], + )?; + values.push(self.b.lam_over( + "__v", + EXPLICIT, + position.validator.lean, + free, + cases, + )); + } + } + } + Ok(values) + } + + /// The minor premise of a family for one constructor of one position: + /// the fields, the induction hypotheses of the recursive ones, then the + /// statement at the constructor. + #[allow(clippy::too_many_arguments)] + fn minor( + &mut self, + boundary: &Boundary, + family: Family, + group: &Group, + position: usize, + ctor: usize, + mode: Mode, + ) -> Result { + let spec = group.positions[position].ctors[ctor].clone(); + let mut xs = Vec::new(); + let mut types = Vec::new(); + for field in &spec.fields { + xs.push(self.b.free()); + types.push(self.lean_ty(field)?); + } + let mut ihs: Vec> = Vec::new(); + let mut ih_types: Vec> = Vec::new(); + for (index, recursion) in spec.recursive.iter().enumerate() { + match (recursion, mode) { + (Some(target), Mode::Ih) => { + ihs.push(Some(self.b.free())); + let claim = self.claim(family, &group.positions[*target], xs[index]); + ih_types.push(Some(claim)); + } + (Some(_), Mode::Unsafe) | (None, _) => { + ihs.push(None); + ih_types.push(None); + } + } + } + let body = match family { + Family::Valid | Family::Inv => { + let mut calls = Vec::new(); + for check in &spec.checks { + let x = xs[check.field]; + let call = match (spec.recursive[check.field], mode) { + (Some(_), Mode::Ih) => { + ihs[check.field].ok_or("a recursive field without a hypothesis")? + } + (Some(target), Mode::Unsafe) => { + let validator = group.positions[target].validator.index; + let head = self.own(&format!("__valid_{validator}._unsafe_rec")); + self.b.app(head, x) + } + (None, _) => { + let head = if family == Family::Valid { + self.valid_const(check.validator) + } else { + self.inv_const(check.validator) + }; + self.b.app(head, x) + } + }; + calls.push(call); + } + if family == Family::Valid { + self.valid_chain(&calls, spec.direct) + } else { + self.inv_chain(&calls, spec.direct) + } + } + Family::Viff => self.viff_chain(&spec, &xs, &ihs)?.proof, + Family::Vrel => self.vrel_body(boundary, group, position, ctor, &xs, &ihs)?, + }; + let mut minor = body; + for (index, hypothesis) in ihs.iter().enumerate().rev() { + if let (Some(free), Some(ty)) = (hypothesis, ih_types[index]) { + minor = self + .b + .lam_over(&format!("__ih{index}"), EXPLICIT, ty, *free, minor); + } + } + for (index, free) in xs.iter().enumerate().rev() { + minor = self.b.lam_over( + &format!("__x{index}"), + EXPLICIT, + types[index], + *free, + minor, + ); + } + Ok(minor) + } + + /// The tests of the checks in order: `if c₁ then if c₂ then … true else + /// false else false`, the last check standing alone when it is the + /// validator's result. + fn valid_chain(&mut self, calls: &[Term], direct: bool) -> Term { + let bool = self.bool_ty(); + let fls = self.fls(); + let (mut out, rest) = match calls.split_last() { + Some((last, rest)) if direct => (*last, rest), + _ => (self.tru(), calls), + }; + for call in rest.iter().rev() { + out = self.ite(bool, *call, out, fls); + } + out + } + + /// The conjunction of the checks' propositions, as [`Self::valid_chain`]. + fn inv_chain(&mut self, calls: &[Term], direct: bool) -> Term { + let (mut out, rest) = match calls.split_last() { + Some((last, rest)) if direct => (*last, rest), + _ => (self.true_prop(), calls), + }; + for call in rest.iter().rev() { + out = self.and_prop(*call, out); + } + out + } + + /// The decision theorem of a constructor: `condAnd_iff` over the checks' + /// theorems. + fn viff_chain( + &mut self, + spec: &CtorSpec, + xs: &[Term], + ihs: &[Option], + ) -> Result { + let mut leaves = Vec::new(); + for check in &spec.checks { + let x = xs[check.field]; + let proof = match (spec.recursive[check.field], ihs[check.field]) { + (Some(_), Some(ih)) => ih, + _ => { + let head = self.viff_const(check.validator); + self.b.app(head, x) + } + }; + let ty = self.viff_claim(check.validator, x); + leaves.push(Pf { proof, ty }); + } + let (mut out, rest) = match leaves.split_last() { + Some((last, rest)) if spec.direct => (*last, rest), + _ => (self.apply("trueIff", &[], &[])?, leaves.as_slice()), + }; + for leaf in rest.iter().rev() { + out = self.apply( + "condAnd_iff", + &[], + &[ + Given::Proof(leaf.proof, leaf.ty), + Given::Proof(out.proof, out.ty), + ], + )?; + } + Ok(out) + } + + // --- The relation of a validator at a constructor ------------------------- + + /// The proof that the program's validator, run on the encoding of a + /// constructor applied to its fields, returns `__valid_k` of it. + fn vrel_body( + &mut self, + boundary: &Boundary, + group: &Group, + position: usize, + ctor: usize, + xs: &[Term], + ihs: &[Option], + ) -> Result { + let entry = &group.positions[position]; + let spec = &entry.ctors[ctor]; + let validator = &entry.validator; + let program = self.gen.program; + let function = program + .functions + .get(validator.index as usize) + .ok_or_else(|| format!("the program has no validator {}", validator.index))?; + let value = self.ctor_value(entry, ctor, xs)?; + let encoded = self.b.app(validator.enc, value); + let mut fields = Vec::new(); + for (ty, x) in spec.fields.iter().zip(xs) { + let enc = self.enc(ty)?; + fields.push(self.b.app(enc, *x)); + } + let empty = self.empty_env(); + let base = self.extend_env(empty, &function.parameters, &[encoded])?; + let mut checks = Vec::new(); + for check in &spec.checks { + let callee = boundary.get(check.validator)?.clone(); + let x = xs[check.field]; + let proof = match (spec.recursive[check.field], ihs[check.field]) { + (Some(_), Some(ih)) => ih, + _ => { + let head = self.vrel_const(check.validator); + self.b.app(head, x) + } + }; + let valid = self.valid_at(check.validator, x); + checks.push(Checked { + validator: callee, + x, + proof, + valid, + }); + } + let parameter = *function + .parameters + .first() + .ok_or("a validator without a parameter")?; + let body = match spec.arm { + Some(arm_index) => { + let (match_ty, scrutinee, arms) = match &function.body { + Expr::Match { + ty, + scrutinee, + arms, + } => (ty, scrutinee.as_ref(), arms.as_slice()), + _ => { + return Err(format!( + "the validator {} was lowered to something other than a match", + validator.index + )); + } + }; + let arm = arms + .get(arm_index) + .ok_or("a validator lowered to fewer arms")?; + let inner = self.extend_env(base, &arm.binders, &fields)?; + let mut vars: Vec<(u64, Term)> = + arm.binders.iter().copied().zip(fields.iter().copied()).collect(); + vars.push((parameter, encoded)); + let binding = Binding { vars, pair: None }; + let derived = self.chain_pf(&binding, inner, &arm.body, &checks, spec.direct)?; + let mut arms_pf = self.arm_hit(base, encoded, arms, arm_index, &fields, inner, derived)?; + for earlier in (0..arm_index).rev() { + arms_pf = self.arm_miss(base, encoded, arms, earlier, arms_pf)?; + } + let name = match scrutinee { + Expr::Var { name } => *name, + _ => return Err("a validator matches on something other than its argument".to_owned()), + }; + let tested = self.variable_pf(base, name, encoded)?; + let t = self.ty_term(match_ty); + let prog = self.prog; + self.apply( + "conv_match", + &[("p", prog), ("env", base), ("t", t)], + &[ + Given::Proof(tested.proof, tested.ty), + Given::Proof(arms_pf.proof, arms_pf.ty), + ], + )? + } + None => { + if fields.len() != 2 { + return Err("a validator without a match over other than a pair".to_owned()); + } + let binding = Binding { + vars: vec![(parameter, encoded)], + pair: Some((fields[0], fields[1])), + }; + self.chain_pf(&binding, base, &function.body, &checks, spec.direct)? + } + }; + let function_term = self.function_term(function)?; + let tru = self.tru(); + let valid = self.valid_at(validator.index, value); + let ctor_bool = self.ts("Value.bool"); + let w = self.b.app(ctor_bool, valid); + let observation = self.rel(tru, w); + let value_ty = self.value_ty(); + let args = self.b.list(value_ty, &[encoded]); + let fi = self.nat_u(validator.index); + let prog = self.prog; + let introduced = self.apply( + "funRel_intro", + &[ + ("p", prog), + ("fi", fi), + ("args", args), + ("o", observation), + ("fn", function_term), + ("env", base), + ], + &[Given::Refl, Given::Refl, Given::Proof(body.proof, body.ty)], + )?; + Ok(introduced.proof) + } + + /// `conv_var` for the variable `name`, bound to `value` in `env`. + fn variable_pf(&mut self, env: Term, name: u64, value: Term) -> Result { + let x = self.nat_u(name); + let prog = self.prog; + self.apply( + "conv_var", + &[("p", prog), ("env", env), ("x", x), ("v", value)], + &[Given::Refl], + ) + } + + /// The proof of an argument of a validator's call: a variable, or a + /// component of the pair it stands for. + fn argument_pf(&mut self, binding: &Binding, env: Term, expr: &Expr) -> Result { + match expr { + Expr::Var { name } => { + let value = binding + .vars + .iter() + .rev() + .find(|(number, _)| number == name) + .map(|(_, value)| *value) + .ok_or_else(|| format!("variable {name} is unbound in a validator"))?; + self.variable_pf(env, *name, value) + } + Expr::First { value } | Expr::Second { value } => { + let (a, b) = binding + .pair + .ok_or("a projection in a validator that checks no pair")?; + let inner = self.argument_pf(binding, env, value)?; + let lemma = if matches!(expr, Expr::First { value: _ }) { + "conv_first" + } else { + "conv_second" + }; + let prog = self.prog; + self.apply( + lemma, + &[("p", prog), ("env", env), ("a", a), ("b", b)], + &[Given::Proof(inner.proof, inner.ty)], + ) + } + Expr::Value { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Match { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::Field { .. } => Err("a validator calls with an argument it does not build".to_owned()), + } + } + + /// `conv_call` of the checked validator on its argument. + fn call_pf( + &mut self, + binding: &Binding, + env: Term, + expr: &Expr, + check: &Checked, + ) -> Result { + let operands = match expr { + Expr::Call { + function, + operands, + } if *function == check.validator.index && operands.len() == 1 => operands, + _ => { + return Err(format!( + "the validator was lowered without the call of {}", + check.validator.index + )); + } + }; + let argument = self.argument_pf(binding, env, &operands[0])?; + let list = self.operands(env, &[argument])?; + let ty = self.vrel_claim(&check.validator, check.x); + let prog = self.prog; + self.apply( + "conv_call", + &[("p", prog), ("env", env)], + &[ + Given::Proof(list.proof, list.ty), + Given::Proof(check.proof, ty), + ], + ) + } + + /// A built Boolean. + fn boolean_pf(&mut self, env: Term, expr: &Expr, value: bool) -> Result { + let (shape, ty) = match expr { + Expr::Build { + shape, + ty, + operands, + } if operands.is_empty() => (*shape, ty), + _ => { + return Err("a validator was lowered without the Boolean it returns".to_owned()); + } + }; + let empty = self.operands(env, &[])?; + let construct = self.apply( + if value { + "construct_true" + } else { + "construct_false" + }, + &[], + &[], + )?; + let shape = self.shape_term(shape); + let ty = self.ty_term(ty); + let prog = self.prog; + self.apply( + "conv_build", + &[("p", prog), ("env", env), ("shape", shape), ("t", ty)], + &[ + Given::Proof(empty.proof, empty.ty), + Given::Proof(construct.proof, construct.ty), + ], + ) + } + + /// The derivation of a constructor's checks: a `cond` per check that is + /// not the result, each testing the callee's relation. + fn chain_pf( + &mut self, + binding: &Binding, + env: Term, + expr: &'a Expr, + checks: &[Checked], + direct: bool, + ) -> Result { + match checks { + [] => self.boolean_pf(env, expr, true), + [only] if direct => self.call_pf(binding, env, expr, only), + [first, rest @ ..] => { + let (condition, then_branch, else_branch) = match expr { + Expr::Cond { + condition, + then_branch, + else_branch, + } => (condition.as_ref(), then_branch.as_ref(), else_branch.as_ref()), + _ => { + return Err( + "a validator was lowered without the test of a check".to_owned() + ); + } + }; + let tested = self.call_pf(binding, env, condition, first)?; + let continued = self.chain_pf(binding, env, then_branch, rest, direct)?; + let refused = self.boolean_pf(env, else_branch, false)?; + let bool = self.bool_ty(); + let (tru, fls) = (self.tru(), self.fls()); + let predicate = self.b.lam("_", EXPLICIT, bool, tru); + let remaining: Vec = rest.iter().map(|check| check.valid).collect(); + let rest_valid = self.valid_chain(&remaining, direct); + let guard = self.b.free(); + let selected = self.ite(bool, guard, rest_valid, fls); + let wrap = self.ts("Value.bool"); + let wrapped = self.b.app(wrap, selected); + let value = self.b.lam_over("__c", EXPLICIT, bool, guard, wrapped); + let on_true = { + let ty = self.eq(bool, first.valid, tru); + self.b.lam("_", EXPLICIT, ty, continued.proof) + }; + let on_false = { + let ty = self.eq(bool, first.valid, fls); + self.b.lam("_", EXPLICIT, ty, refused.proof) + }; + let then_term = self.expr_term(then_branch)?; + let else_term = self.expr_term(else_branch)?; + let prog = self.prog; + self.apply( + "conv_cond", + &[ + ("p", prog), + ("env", env), + ("t", then_term), + ("e", else_term), + ], + &[ + Given::Term(predicate), + Given::Term(value), + Given::Term(first.valid), + Given::Proof(tested.proof, tested.ty), + Given::Term(on_true), + Given::Term(on_false), + ], + ) + } + } + } + + // --- The entry ---------------------------------------------------------- + + /// `Bool.casesOn x` proving `Eq Bool (shape x) x` by `rfl` at each + /// value: `shape` is a Boolean expression over `x` that is `x` at + /// `false` and `true`. + fn bool_cases_eq(&mut self, x: Term, shape: Term) -> Result { + let bool = self.bool_ty(); + let free = self.b.free(); + let abstracted = self.b.substitute(shape, &[x], &[free]); + let claim = self.eq(bool, abstracted, free); + let motive = self.b.lam_over("x", EXPLICIT, bool, free, claim); + let (tru, fls) = (self.tru(), self.fls()); + let (on_false, on_true) = (self.refl(bool, fls), self.refl(bool, tru)); + self.cases_on( + &SemanticType::Bool, + x, + motive, + CoreLevel::Zero, + &[on_false, on_true], + ) + } + + /// The fits and value the entry computes over validators' tests + /// `valid[i]` of the checked parameters, in parameter order. + fn entry_chain(&mut self, sig: &Sig, tests: &[Term]) -> (Term, Term) { + let bool = self.bool_ty(); + let value_ty = self.value_ty(); + let (tru, none) = (self.tru(), self.ts("Value.none")); + let some = self.ts("Value.some"); + let mut fits = sig.fits_applied; + let mut outcome = self.b.app(some, sig.value); + for test in tests.iter().rev() { + fits = self.ite(bool, *test, fits, tru); + outcome = self.ite(value_ty, *test, outcome, none); + } + (fits, outcome) + } + + /// `cond (fits) (Obs.value outcome) Obs.overflow`. + fn entry_observation(&mut self, fits: Term, outcome: Term) -> Term { + let obs = self.lp("Obs"); + let wrap = self.lp("Obs.value"); + let overflow = self.lp("Obs.overflow"); + let valued = self.b.app(wrap, outcome); + let cond = self.b.konst1("cond"); + self.b.apps(cond, &[obs, fits, valued, overflow]) + } + + /// `Eq Obs observation rhs`. + fn obs_eq(&mut self, observation: Term, rhs: Term) -> Term { + let obs = self.lp("Obs"); + self.eq(obs, observation, rhs) + } + + /// The entry's rows and its three theorems. + #[allow(clippy::too_many_lines)] + fn entry_section( + &mut self, + boundary: &Boundary, + entry: u64, + validators: &[Option], + sig: &Sig, + parameters: &[SemanticType], + ) -> Result<(), String> { + if validators.len() != sig.fits_binders.len() || validators.len() != parameters.len() { + return Err("the entry validates a parameter list of another length".to_owned()); + } + let program = self.gen.program; + let function = program + .functions + .get(entry as usize) + .ok_or_else(|| format!("the program has no entry {entry}"))?; + let frees: Vec = sig.fits_binders.iter().map(|binder| binder.free).collect(); + // The checked parameters, in order, with their validators. + let checked: Vec<(usize, u64)> = validators + .iter() + .enumerate() + .filter_map(|(position, validator)| validator.map(|validator| (position, validator))) + .collect(); + let mut tests = Vec::new(); + let mut accepts_terms = Vec::new(); + for (position, validator) in &checked { + tests.push(self.valid_at(*validator, frees[*position])); + let inv = self.inv_const(*validator); + accepts_terms.push(self.b.app(inv, frees[*position])); + } + let (fits, outcome) = self.entry_chain(sig, &tests); + let mut accepts = self.true_prop(); + for term in accepts_terms.iter().rev() { + accepts = self.and_prop(*term, accepts); + } + let bool = self.bool_ty(); + let value_ty = self.value_ty(); + let obs = self.lp("Obs"); + let prop = self.b.prop(); + // entryFits, entryValue, denoteEntry, accepts. + let fits_value = self.lambdas(&sig.fits_binders, fits); + let fits_ty = self.pis(&sig.fits_binders, bool); + self.push_row( + "entryFits", + RowKind::Definition { compiled: true }, + fits_ty, + fits_value, + )?; + let outcome_value = self.lambdas(&sig.fits_binders, outcome); + let outcome_ty = self.pis(&sig.fits_binders, value_ty); + self.push_row( + "entryValue", + RowKind::Definition { compiled: true }, + outcome_ty, + outcome_value, + )?; + let entry_fits = self.own("entryFits"); + let entry_fits_at = self.b.apps(entry_fits, &frees); + let entry_value = self.own("entryValue"); + let entry_value_at = self.b.apps(entry_value, &frees); + let observed = self.entry_observation(entry_fits_at, entry_value_at); + let denote_value = self.lambdas(&sig.fits_binders, observed); + let denote_ty = self.pis(&sig.fits_binders, obs); + self.push_row( + "denoteEntry", + RowKind::Definition { compiled: true }, + denote_ty, + denote_value, + )?; + let accepts_value = self.lambdas(&sig.fits_binders, accepts); + let accepts_ty = self.pis(&sig.fits_binders, prop); + self.push_row( + "accepts", + RowKind::Definition { compiled: false }, + accepts_ty, + accepts_value, + )?; + let denote_entry = self.own("denoteEntry"); + let denote_entry_at = self.b.apps(denote_entry, &frees); + let accepts_head = self.own("accepts"); + let accepts_at = self.b.apps(accepts_head, &frees); + // entry. + let mut encoded = Vec::new(); + for (parameter, free) in parameters.iter().zip(&frees) { + let enc = self.enc(parameter)?; + encoded.push(self.b.app(enc, *free)); + } + let empty = self.empty_env(); + let env = self.extend_env(empty, &function.parameters, &encoded)?; + let mut layers: Vec<(usize, u64, &'a Expr, &'a Expr)> = Vec::new(); + let mut cursor = &function.body; + for (position, validator) in &checked { + match cursor { + Expr::Cond { + condition, + then_branch, + else_branch, + } => { + match condition.as_ref() { + Expr::Call { + function: callee, + operands, + } if callee == validator && operands.len() == 1 => {} + _ => { + return Err(format!( + "the entry was lowered without the call of the validator {validator}" + )); + } + } + layers.push((*position, *validator, then_branch.as_ref(), else_branch.as_ref())); + cursor = then_branch.as_ref(); + } + _ => return Err("the entry was lowered without a test of a validator".to_owned()), + } + } + let (shape, outcome_ty_program) = match cursor { + Expr::Build { + shape, + ty, + operands, + } if operands.len() == 1 => (*shape, ty), + _ => return Err("the entry was lowered without the root's call".to_owned()), + }; + let mut argument_pfs = Vec::new(); + for (position, number) in function.parameters.iter().enumerate() { + argument_pfs.push(self.variable_pf(env, *number, encoded[position])?); + } + let argument_list = self.operands(env, &argument_pfs)?; + let prog = self.prog; + let rel_head = self.own("__rel_0"); + let rel_proof = self.b.apps(rel_head, &sig.rel_params); + let called = self.apply( + "conv_call", + &[("p", prog), ("env", env)], + &[ + Given::Proof(argument_list.proof, argument_list.ty), + Given::Proof(rel_proof, sig.statement), + ], + )?; + let call_list = self.operands(env, &[called])?; + let construct = self.apply("construct_some", &[("a", sig.value)], &[])?; + let shape_term = self.shape_term(shape); + let outcome_ty_term = self.ty_term(outcome_ty_program); + let built = self.apply( + "conv_build", + &[ + ("p", prog), + ("env", env), + ("shape", shape_term), + ("t", outcome_ty_term), + ], + &[ + Given::Proof(call_list.proof, call_list.ty), + Given::Proof(construct.proof, construct.ty), + ], + )?; + // The call's width predicate is conjoined with `true` twice more; at + // each value of the predicate that is the predicate. + let built_fits = self.fits_of(built)?; + let equation = self.bool_cases_eq(sig.fits_applied, built_fits)?; + let mut current = self.apply( + "Conv.fits_eq", + &[("f'", sig.fits_applied)], + &[ + Given::Proof(built.proof, built.ty), + Given::Term(equation), + ], + )?; + let some = self.ts("Value.some"); + let mut fits_inside = sig.fits_applied; + let mut outcome_inside = self.b.app(some, sig.value); + let none = self.ts("Value.none"); + let tru = self.tru(); + for (position, validator, then_branch, else_branch) in layers.iter().rev() { + let callee = boundary.get(*validator)?.clone(); + let x = frees[*position]; + let guard = self.b.free(); + let on_fits = self.ite(bool, guard, fits_inside, tru); + let predicate = self.b.lam_over("__c", EXPLICIT, bool, guard, on_fits); + let guard = self.b.free(); + let on_value = self.ite(value_ty, guard, outcome_inside, none); + let value = self.b.lam_over("__c", EXPLICIT, bool, guard, on_value); + let test = self.valid_at(*validator, x); + // The test: the validator's relation at the parameter. + let argument = self.variable_pf(env, function.parameters[*position], encoded[*position])?; + let list = self.operands(env, &[argument])?; + let relation_head = self.vrel_const(*validator); + let relation = self.b.app(relation_head, x); + let relation_ty = self.vrel_claim(&callee, x); + let tested = self.apply( + "conv_call", + &[("p", prog), ("env", env)], + &[ + Given::Proof(list.proof, list.ty), + Given::Proof(relation, relation_ty), + ], + )?; + let refused = self.refusal_pf(env, else_branch)?; + let fls = self.fls(); + let on_true = { + let ty = self.eq(bool, test, tru); + self.b.lam("_", EXPLICIT, ty, current.proof) + }; + let on_false = { + let ty = self.eq(bool, test, fls); + self.b.lam("_", EXPLICIT, ty, refused.proof) + }; + let then_term = self.expr_term(then_branch)?; + let else_term = self.expr_term(else_branch)?; + current = self.apply( + "conv_cond", + &[ + ("p", prog), + ("env", env), + ("t", then_term), + ("e", else_term), + ], + &[ + Given::Term(predicate), + Given::Term(value), + Given::Term(test), + Given::Proof(tested.proof, tested.ty), + Given::Term(on_true), + Given::Term(on_false), + ], + )?; + fits_inside = self.ite(bool, test, fits_inside, tru); + outcome_inside = self.ite(value_ty, test, outcome_inside, none); + } + let function_term = self.function_term(function)?; + let entry_index = self.nat_u(entry); + let introduced = self.apply( + "funRel_intro", + &[ + ("p", prog), + ("fi", entry_index), + ("args", sig.arguments), + ("o", denote_entry_at), + ("fn", function_term), + ("env", env), + ], + &[Given::Refl, Given::Refl, Given::Proof(current.proof, current.ty)], + )?; + let run = self.apply( + "run_of_funRel", + &[], + &[Given::Proof(introduced.proof, introduced.ty)], + )?; + let run_conv = self.lp("RunConv"); + let statement = self + .b + .apps(run_conv, &[prog, entry_index, sig.arguments, denote_entry_at]); + let entry_value_term = self.lambdas(&sig.rel_binders, run.proof); + let entry_ty = self.pis(&sig.rel_binders, statement); + self.push_row("entry", RowKind::Theorem, entry_ty, entry_value_term)?; + // entry_accepts and entry_refuses. + let denote_head = self.own("denote"); + let denote_at = self.b.apps(denote_head, &frees); + let some_obs = self.lp("someObs"); + let accepted_obs = self.b.app(some_obs, denote_at); + let hypothesis = self.b.free(); + let accepts_proof = self.entry_accepts_proof( + sig, + &checked, + &frees, + &tests, + &accepts_terms, + hypothesis, + )?; + let accepted = self.obs_eq(denote_entry_at, accepted_obs); + let accepted_pi = self + .b + .pi_over("__h", EXPLICIT, accepts_at, hypothesis, accepted); + let accepted_ty = self.pis(&sig.fits_binders, accepted_pi); + let accepted_proof = + self.b + .lam_over("__h", EXPLICIT, accepts_at, hypothesis, accepts_proof); + let accepted_value = self.lambdas(&sig.fits_binders, accepted_proof); + self.push_row( + "entry_accepts", + RowKind::Theorem, + accepted_ty, + accepted_value, + )?; + let hypothesis = self.b.free(); + let refusal = self.entry_refuses_proof( + sig, + &checked, + &frees, + &tests, + &accepts_terms, + hypothesis, + )?; + let not = self.b.konst("Not"); + let refused_hypothesis = self.b.app(not, accepts_at); + let value_wrap = self.lp("Obs.value"); + let none_value = self.b.app(value_wrap, none); + let refused = self.obs_eq(denote_entry_at, none_value); + let refused_pi = self + .b + .pi_over("__h", EXPLICIT, refused_hypothesis, hypothesis, refused); + let refused_ty = self.pis(&sig.fits_binders, refused_pi); + let refused_proof = + self.b + .lam_over("__h", EXPLICIT, refused_hypothesis, hypothesis, refusal); + let refused_value = self.lambdas(&sig.fits_binders, refused_proof); + self.push_row("entry_refuses", RowKind::Theorem, refused_ty, refused_value) + } + + /// The program's refusal: `build none`. + fn refusal_pf(&mut self, env: Term, expr: &Expr) -> Result { + let (shape, ty) = match expr { + Expr::Build { + shape, + ty, + operands, + } if operands.is_empty() => (*shape, ty), + _ => return Err("the entry was lowered without its refusal".to_owned()), + }; + let empty = self.operands(env, &[])?; + let construct = self.apply("construct_none", &[], &[])?; + let shape = self.shape_term(shape); + let ty = self.ty_term(ty); + let prog = self.prog; + self.apply( + "conv_build", + &[("p", prog), ("env", env), ("shape", shape), ("t", ty)], + &[ + Given::Proof(empty.proof, empty.ty), + Given::Proof(construct.proof, construct.ty), + ], + ) + } + + /// `Eq Obs (cond (fits tests) (value (outcome tests)) overflow) rhs`. + fn entry_goal(&mut self, sig: &Sig, tests: &[Term], rhs: Term) -> Term { + let (fits, outcome) = self.entry_chain(sig, tests); + let observed = self.entry_observation(fits, outcome); + self.obs_eq(observed, rhs) + } + + /// The proof that the entry returns the root's observation, as `some`, + /// when every proposition holds: each test is rewritten to `true` along + /// the decision theorem, from the base case the width predicate splits. + fn entry_accepts_proof( + &mut self, + sig: &Sig, + checked: &[(usize, u64)], + frees: &[Term], + tests: &[Term], + invariants: &[Term], + hypothesis: Term, + ) -> Result { + let obs = self.lp("Obs"); + let denote_head = self.own("denote"); + let denote_at = self.b.apps(denote_head, frees); + let some_obs = self.lp("someObs"); + let rhs = self.b.app(some_obs, denote_at); + // The base case: every test is `true`. + let trues: Vec = tests.iter().map(|_| self.tru()).collect(); + let free = self.b.free(); + let some = self.ts("Value.some"); + let outcome = self.b.app(some, sig.value); + let unconditional = self.entry_observation(free, outcome); + let plain = self.entry_observation(free, sig.value); + let some_plain = self.b.app(some_obs, plain); + let claim = self.obs_eq(unconditional, some_plain); + let bool = self.bool_ty(); + let motive = self.b.lam_over("x", EXPLICIT, bool, free, claim); + let (tru, fls) = (self.tru(), self.fls()); + let overflow_case = { + let observed = self.entry_observation(fls, outcome); + self.refl(obs, observed) + }; + let value_case = { + let observed = self.entry_observation(tru, outcome); + self.refl(obs, observed) + }; + let base = self.cases_on( + &SemanticType::Bool, + sig.fits_applied, + motive, + CoreLevel::Zero, + &[overflow_case, value_case], + )?; + let _ = (checked, obs); + let mut proof = base; + let mut callers = trues; + // The invariants' conjunction, taken apart from the first. + let mut rest = hypothesis; + let mut suffixes: Vec = Vec::new(); + { + let mut tail = self.true_prop(); + for term in invariants.iter().rev() { + suffixes.push(tail); + tail = self.and_prop(*term, tail); + } + suffixes.reverse(); + } + for (index, ((_, validator), test)) in checked.iter().zip(tests).enumerate() { + let invariant = invariants[index]; + let suffix = suffixes[index]; + let left = { + let head = self.b.konst("And.left"); + self.b.apps(head, &[invariant, suffix, rest]) + }; + let next = { + let head = self.b.konst("And.right"); + self.b.apps(head, &[invariant, suffix, rest]) + }; + let viff = { + let head = self.viff_const(*validator); + let at = self.b.app(head, frees[checked[index].0]); + at + }; + let decided = self.is_true(*test); + let equation = { + let head = self.b.konst("Iff.mpr"); + self.b.apps(head, &[decided, invariant, viff, left]) + }; + // Rewrite position `index` of the goal from `true` to the test. + let free = self.b.free(); + let mut moved = callers.clone(); + moved[index] = free; + let goal = self.entry_goal(sig, &moved, rhs); + let motive = self.b.lam_over("__b", EXPLICIT, bool, free, goal); + let symmetric = { + let head = self.b.konst1("Eq.symm"); + self.b.apps(head, &[bool, *test, tru, equation]) + }; + let rewrite = self.b.konst_at( + "Eq.ndrec", + vec![ + CoreLevel::Zero, + CoreLevel::Succ(Box::new(CoreLevel::Zero)), + ], + ); + proof = self + .b + .apps(rewrite, &[bool, tru, motive, proof, *test, symmetric]); + callers[index] = *test; + rest = next; + } + Ok(proof) + } + + /// The proof that the entry returns `none` when a proposition fails: for + /// all Booleans in place of the tests, by cases on each in turn, with the + /// remaining tests' being irrelevant once one is `false`. + fn entry_refuses_proof( + &mut self, + sig: &Sig, + checked: &[(usize, u64)], + frees: &[Term], + tests: &[Term], + invariants: &[Term], + hypothesis: Term, + ) -> Result { + let bool = self.bool_ty(); + let (tru, fls) = (self.tru(), self.fls()); + let none = self.ts("Value.none"); + let wrap = self.lp("Obs.value"); + let rhs = self.b.app(wrap, none); + let obs = self.lp("Obs"); + let count = tests.len(); + let variables: Vec = (0..count).map(|_| self.b.free()).collect(); + // The conjunction of `b = true` over `b`s. + let all_true = |writer: &mut Self, bs: &[Term]| -> Term { + let mut out = writer.true_prop(); + for b in bs.iter().rev() { + let eq = writer.is_true(*b); + out = writer.and_prop(eq, out); + } + out + }; + // The proof of `all_true` at `trues`. + let truth_proof = |writer: &mut Self, length: usize| -> Term { + let mut out = writer.b.konst("True.intro"); + let eq = { + let t = writer.tru(); + writer.is_true(t) + }; + let trues: Vec = (0..length).map(|_| writer.tru()).collect(); + for index in (0..length).rev() { + let rest = all_true(writer, &trues[index + 1..]); + let refl = { + let t = writer.tru(); + writer.refl(bool, t) + }; + let intro = writer.b.konst("And.intro"); + out = writer.b.apps(intro, &[eq, rest, refl, out]); + } + out + }; + // `refuse(j)`: for the first `j` tests `true` and the rest the + // variables, the proof of `¬ all_true → goal`. + let mut next = { + // j = count: every test is true, so the hypothesis is refuted. + let trues: Vec = (0..count).map(|_| self.tru()).collect(); + let goal = self.entry_goal(sig, &trues, rhs); + let accepted = all_true(self, &trues); + let not = self.b.konst("Not"); + let refuted = self.b.app(not, accepted); + let witness = truth_proof(self, count); + let absurd = self.b.konst_at("absurd", vec![CoreLevel::Zero]); + let free = self.b.free(); + let body = self.b.apps(absurd, &[accepted, goal, witness, free]); + self.b.lam_over("__n", EXPLICIT, refuted, free, body) + }; + for index in (0..count).rev() { + let mut callers: Vec = (0..index).map(|_| self.tru()).collect(); + callers.push(variables[index]); + callers.extend(variables[index + 1..].iter().copied()); + let goal = self.entry_goal(sig, &callers, rhs); + let accepted = all_true(self, &callers); + let not = self.b.konst("Not"); + let refuted = self.b.app(not, accepted); + let claim = self.b.arrow(refuted, goal); + // The motive abstracts the variable at `index`. + let abstracted = self.b.lam_over("__b", EXPLICIT, bool, variables[index], claim); + // `false`: the goal holds whatever follows. + let mut at_false = callers.clone(); + at_false[index] = fls; + let false_goal = self.entry_goal(sig, &at_false, rhs); + let false_accepted = all_true(self, &at_false); + let false_refuted = self.b.app(not, false_accepted); + let on_false = { + let free = self.b.free(); + let value_wrap = self.lp("Obs.value"); + let value = self.b.app(value_wrap, none); + let refl = self.refl(obs, value); + let _ = false_goal; + self.b.lam_over("__n", EXPLICIT, false_refuted, free, refl) + }; + let cases = self.cases_on( + &SemanticType::Bool, + variables[index], + abstracted, + CoreLevel::Zero, + &[on_false, next], + )?; + next = cases; + } + // Instantiate the variables at the tests. + let mut lemma = next; + for variable in variables.iter().rev() { + lemma = self.b.lam_over("__b", EXPLICIT, bool, *variable, lemma); + } + let applied = self.b.apps(lemma, tests); + // `¬ all_true tests`, from the hypothesis `¬ accepts`. + let accepted = all_true(self, tests); + let hold = self.b.free(); + let mut inv_proof = self.b.konst("True.intro"); + // Build the invariants from the equations, last first. + let mut suffixes: Vec = Vec::new(); + { + let mut tail = self.true_prop(); + for term in invariants.iter().rev() { + suffixes.push(tail); + tail = self.and_prop(*term, tail); + } + suffixes.reverse(); + } + // The conjunction of equations, taken apart from the first. + let mut equations: Vec = Vec::new(); + let mut cursor = hold; + for index in 0..count { + let decided = self.is_true(tests[index]); + let tail = all_true(self, &tests[index + 1..]); + let left = { + let head = self.b.konst("And.left"); + self.b.apps(head, &[decided, tail, cursor]) + }; + let right = { + let head = self.b.konst("And.right"); + self.b.apps(head, &[decided, tail, cursor]) + }; + equations.push(left); + cursor = right; + } + for index in (0..count).rev() { + let decided = self.is_true(tests[index]); + let viff = { + let head = self.viff_const(checked[index].1); + self.b.app(head, frees[checked[index].0]) + }; + let invariant = invariants[index]; + let holds = { + let head = self.b.konst("Iff.mp"); + self.b.apps(head, &[decided, invariant, viff, equations[index]]) + }; + let intro = self.b.konst("And.intro"); + inv_proof = self + .b + .apps(intro, &[invariant, suffixes[index], holds, inv_proof]); + } + let denied = self.b.app(hypothesis, inv_proof); + let refutation = self.b.lam_over("__a", EXPLICIT, accepted, hold, denied); + Ok(self.b.app(applied, refutation)) + } +} + +// TEMP-SHIM-BEGIN +impl Writer<'_, '_> { + fn key_order(&mut self, site: &Site, ty: &SemanticType) -> Result { + let module = self.gen.source.lean_module(&site.module)?; + let instance = match ty { + SemanticType::Nat => "instKeyNat", + SemanticType::Int => "instKeyInt", + _ => return Err("temporary".to_owned()), + }; + let lean = self.lean_ty(ty)?; + let instance = self.b.konst(&format!("{module}.LexLeanCollections.{instance}")); + let compare = self + .b + .konst(&format!("{module}.LexLeanCollections.Key.compare")); + Ok(self.b.apps(compare, &[lean, instance])) + } + fn key_spec(&mut self, site: &Site, ty: &SemanticType) -> Result { + let order = self.key_order(site, ty)?; + let lemma = match ty { + SemanticType::Nat => "keySpec_nat", + SemanticType::Int => "keySpec_int", + _ => return Err("temporary".to_owned()), + }; + self.apply(lemma, &[], &[Given::Term(order), Given::Refl]) } } +// TEMP-SHIM-END From dd5ebe3229d3cc344bd99bc8ccfc1dab83383ec9 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 18:08:39 +0000 Subject: [PATCH 13/41] #89 stage 3: certificate A data: boundary validators in the crate's exhaustive-match style --- .../production/certificate/data_a/boundary.rs | 372 +++++++++--------- 1 file changed, 193 insertions(+), 179 deletions(-) diff --git a/crates/lexlean/src/production/certificate/data_a/boundary.rs b/crates/lexlean/src/production/certificate/data_a/boundary.rs index d07d7f468..700a0de31 100644 --- a/crates/lexlean/src/production/certificate/data_a/boundary.rs +++ b/crates/lexlean/src/production/certificate/data_a/boundary.rs @@ -433,9 +433,8 @@ impl<'a> Writer<'_, 'a> { let head = self.lp("ascSet_inv"); self.b.apps(head, &[kappa, cmp]) }; - let element = match parameter { - Ty::List { element } => element.as_ref(), - other => return Err(format!("a set validator over {other:?}")), + let Ty::List { element } = parameter else { + return Err("a set validator over something other than a list".to_owned()); }; let t = self.ty_term(element); hints.extend([("κ", kappa), ("ek", ek), ("cmp", cmp), ("t", t)]); @@ -477,14 +476,13 @@ impl<'a> Writer<'_, 'a> { let head = self.lp("ascMap_inv"); self.b.apps(head, &[kappa, nu, cmp, valid_v, inv_v, viff_v]) }; - let (tk, tw) = match parameter { - Ty::List { element } => match element.as_ref() { - Ty::Pair { left, right } => (left.as_ref(), right.as_ref()), - other => return Err(format!("a map validator over entries {other:?}")), - }, - other => return Err(format!("a map validator over {other:?}")), + let Ty::List { element } = parameter else { + return Err("a map validator over something other than a list".to_owned()); }; - let (tk, tw) = (self.ty_term(tk), self.ty_term(tw)); + let Ty::Pair { left, right } = element.as_ref() else { + return Err("a map validator over entries that are not pairs".to_owned()); + }; + let (tk, tw) = (self.ty_term(left), self.ty_term(right)); let fv = self.nat_u(*value); hints.extend([ ("κ", kappa), @@ -532,10 +530,10 @@ impl<'a> Writer<'_, 'a> { let head = self.lp("validList_inv"); self.b.apps(head, &[alpha, valid_e, inv_e, viff_e]) }; - let t = match parameter { - Ty::List { element } => self.ty_term(element), - other => return Err(format!("a list validator over {other:?}")), + let Ty::List { element: stored } = parameter else { + return Err("a list validator over something other than a list".to_owned()); }; + let t = self.ty_term(stored); let fv = self.nat_u(*element); hints.extend([("α", alpha), ("ea", ea), ("fv", fv), ("t", t)]); let applied = self.apply( @@ -572,10 +570,12 @@ impl<'a> Writer<'_, 'a> { let head = self.lp("validOption_inv"); self.b.apps(head, &[alpha, valid_v, inv_v, viff_v]) }; - let t = match parameter { - Ty::Option { value } => self.ty_term(value), - other => return Err(format!("an option validator over {other:?}")), + let Ty::Option { value: stored } = parameter else { + return Err( + "an option validator over something other than an option".to_owned() + ); }; + let t = self.ty_term(stored); let fv = self.nat_u(*value); hints.extend([("α", alpha), ("ea", ea), ("fv", fv), ("t", t)]); let applied = self.apply( @@ -615,10 +615,14 @@ impl<'a> Writer<'_, 'a> { &[alpha, beta, valid_a, valid_b, inv_a, inv_b, viff_a, viff_b], ) }; - let (ta, tb) = match parameter { - Ty::Pair { left, right } => (self.ty_term(left), self.ty_term(right)), - other => return Err(format!("a pair validator over {other:?}")), + let Ty::Pair { + left: first, + right: second, + } = parameter + else { + return Err("a pair validator over something other than a pair".to_owned()); }; + let (ta, tb) = (self.ty_term(first), self.ty_term(second)); let (fa, fb) = (self.nat_u(*left), self.nat_u(*right)); hints.extend([ ("α", alpha), @@ -666,13 +670,19 @@ impl<'a> Writer<'_, 'a> { let head = self.lp("validExcept_inv"); self.b.apps( head, - &[epsilon, alpha, valid_e, valid_a, inv_e, inv_a, viff_e, viff_a], + &[ + epsilon, alpha, valid_e, valid_a, inv_e, inv_a, viff_e, viff_a, + ], ) }; - let (ta, te) = match parameter { - Ty::Result { ok, error } => (self.ty_term(ok), self.ty_term(error)), - other => return Err(format!("a result validator over {other:?}")), + let Ty::Result { + ok: stored_ok, + error: stored_error, + } = parameter + else { + return Err("a result validator over something other than a result".to_owned()); }; + let (ta, te) = (self.ty_term(stored_ok), self.ty_term(stored_error)); let (fa, fe) = (self.nat_u(*ok), self.nat_u(*error)); hints.extend([ ("α", alpha), @@ -863,11 +873,7 @@ impl<'a> Writer<'_, 'a> { /// The validators of a recursive group of document types and the /// containers nested in them, which are mutually structural. #[allow(clippy::too_many_lines)] - fn recursive_validators( - &mut self, - boundary: &Boundary, - members: &[u64], - ) -> Result<(), String> { + fn recursive_validators(&mut self, boundary: &Boundary, members: &[u64]) -> Result<(), String> { let document = members .iter() .find_map(|index| { @@ -889,7 +895,9 @@ impl<'a> Writer<'_, 'a> { .iter() .filter_map(|index| boundary.validators.get(index)) .find(|validator| self.gen.source.type_text(&validator.ty) == wanted) - .ok_or_else(|| format!("`{wanted}` is in a recursive group but has no validator"))?; + .ok_or_else(|| { + format!("`{wanted}` is in a recursive group but has no validator") + })?; positions.push(GroupPosition { validator: validator.clone(), ctors: self.constructor_specs(validator, &entry.constructors)?, @@ -936,7 +944,10 @@ impl<'a> Writer<'_, 'a> { }; let mut order: Vec<(u64, usize)> = Vec::new(); for member in members { - order.push((*member, position_of(*member).ok_or("a member outside the group")?)); + order.push(( + *member, + position_of(*member).ok_or("a member outside the group")?, + )); } let valid = self.family_values(boundary, Family::Valid, &group)?; let mut names = Vec::new(); @@ -1039,13 +1050,9 @@ impl<'a> Writer<'_, 'a> { let step = self .b .lam_over("__x1", EXPLICIT, list_ty, tail_x, step_body); - let step = self - .b - .lam_over("__x0", EXPLICIT, element_ty, head_x, step); + let step = self.b.lam_over("__x0", EXPLICIT, element_ty, head_x, step); let lemma = self.lp("invList_eqs"); - let proof = self - .b - .apps(lemma, &[alpha, inv_e, inv_k, base, step, x]); + let proof = self.b.apps(lemma, &[alpha, inv_e, inv_k, base, step, x]); (library, proof) } Validation::Option { value } => { @@ -1090,9 +1097,7 @@ impl<'a> Writer<'_, 'a> { }; let claim = self.iff(inv_at, library); let ty = self.b.pi_over("__v", EXPLICIT, validator.lean, x, claim); - let value = self - .b - .lam_over("__v", EXPLICIT, validator.lean, x, proof); + let value = self.b.lam_over("__v", EXPLICIT, validator.lean, x, proof); self.push_row( &format!("__vinv_{}", validator.index), RowKind::Theorem, @@ -1126,13 +1131,9 @@ impl<'a> Writer<'_, 'a> { let head = self.b.konst("Iff.refl"); let mut minor = self.b.app(head, at); for (index, free) in frees.iter().enumerate().rev() { - minor = self.b.lam_over( - &format!("__x{index}"), - EXPLICIT, - types[index], - *free, - minor, - ); + minor = + self.b + .lam_over(&format!("__x{index}"), EXPLICIT, types[index], *free, minor); } minors.push(minor); } @@ -1149,9 +1150,10 @@ impl<'a> Writer<'_, 'a> { let ty = &position.validator.ty; match &position.validator.kind { Validation::Document { fields: _ } => { - let (member, arguments) = match ty { - SemanticType::Named { member, arguments } => (member, arguments), - other => return Err(format!("a document validator over {other:?}")), + let SemanticType::Named { member, arguments } = ty else { + return Err( + "a document validator over a type that is not a document".to_owned() + ); }; let shape = self.gen.source.document(ty)?; let name = shape @@ -1212,9 +1214,9 @@ impl<'a> Writer<'_, 'a> { all.extend_from_slice(fields); Ok(self.b.apps(head, &all)) } - Validation::Trivial | Validation::Set | Validation::Map { value: _ } => Err( - "a validator without constructors".to_owned(), - ), + Validation::Trivial | Validation::Set | Validation::Map { value: _ } => { + Err("a validator without constructors".to_owned()) + } } } @@ -1380,13 +1382,9 @@ impl<'a> Writer<'_, 'a> { } } for (index, free) in xs.iter().enumerate().rev() { - minor = self.b.lam_over( - &format!("__x{index}"), - EXPLICIT, - types[index], - *free, - minor, - ); + minor = self + .b + .lam_over(&format!("__x{index}"), EXPLICIT, types[index], *free, minor); } Ok(minor) } @@ -1512,36 +1510,42 @@ impl<'a> Writer<'_, 'a> { .ok_or("a validator without a parameter")?; let body = match spec.arm { Some(arm_index) => { - let (match_ty, scrutinee, arms) = match &function.body { - Expr::Match { - ty, - scrutinee, - arms, - } => (ty, scrutinee.as_ref(), arms.as_slice()), - _ => { - return Err(format!( - "the validator {} was lowered to something other than a match", - validator.index - )); - } + let Expr::Match { + ty: match_ty, + scrutinee, + arms, + } = &function.body + else { + return Err(format!( + "the validator {} was lowered to something other than a match", + validator.index + )); }; + let (scrutinee, arms) = (scrutinee.as_ref(), arms.as_slice()); let arm = arms .get(arm_index) .ok_or("a validator lowered to fewer arms")?; let inner = self.extend_env(base, &arm.binders, &fields)?; - let mut vars: Vec<(u64, Term)> = - arm.binders.iter().copied().zip(fields.iter().copied()).collect(); + let mut vars: Vec<(u64, Term)> = arm + .binders + .iter() + .copied() + .zip(fields.iter().copied()) + .collect(); vars.push((parameter, encoded)); let binding = Binding { vars, pair: None }; let derived = self.chain_pf(&binding, inner, &arm.body, &checks, spec.direct)?; - let mut arms_pf = self.arm_hit(base, encoded, arms, arm_index, &fields, inner, derived)?; + let mut arms_pf = + self.arm_hit(base, encoded, arms, arm_index, &fields, inner, derived)?; for earlier in (0..arm_index).rev() { arms_pf = self.arm_miss(base, encoded, arms, earlier, arms_pf)?; } - let name = match scrutinee { - Expr::Var { name } => *name, - _ => return Err("a validator matches on something other than its argument".to_owned()), + let Expr::Var { name } = scrutinee else { + return Err( + "a validator matches on something other than its argument".to_owned() + ); }; + let name = *name; let tested = self.variable_pf(base, name, encoded)?; let t = self.ty_term(match_ty); let prog = self.prog; @@ -1641,7 +1645,9 @@ impl<'a> Writer<'_, 'a> { | Expr::Closure { .. } | Expr::Apply { .. } | Expr::Prim { .. } - | Expr::Field { .. } => Err("a validator calls with an argument it does not build".to_owned()), + | Expr::Field { .. } => { + Err("a validator calls with an argument it does not build".to_owned()) + } } } @@ -1653,18 +1659,18 @@ impl<'a> Writer<'_, 'a> { expr: &Expr, check: &Checked, ) -> Result { - let operands = match expr { - Expr::Call { - function, - operands, - } if *function == check.validator.index && operands.len() == 1 => operands, - _ => { - return Err(format!( - "the validator was lowered without the call of {}", - check.validator.index - )); - } + let Expr::Call { function, operands } = expr else { + return Err(format!( + "the validator was lowered without the call of {}", + check.validator.index + )); }; + if *function != check.validator.index || operands.len() != 1 { + return Err(format!( + "the validator was lowered with another call than {}", + check.validator.index + )); + } let argument = self.argument_pf(binding, env, &operands[0])?; let list = self.operands(env, &[argument])?; let ty = self.vrel_claim(&check.validator, check.x); @@ -1681,16 +1687,18 @@ impl<'a> Writer<'_, 'a> { /// A built Boolean. fn boolean_pf(&mut self, env: Term, expr: &Expr, value: bool) -> Result { - let (shape, ty) = match expr { - Expr::Build { - shape, - ty, - operands, - } if operands.is_empty() => (*shape, ty), - _ => { - return Err("a validator was lowered without the Boolean it returns".to_owned()); - } + let Expr::Build { + shape, + ty, + operands, + } = expr + else { + return Err("a validator was lowered without the Boolean it returns".to_owned()); }; + if !operands.is_empty() { + return Err("a validator builds a Boolean from operands".to_owned()); + } + let shape = *shape; let empty = self.operands(env, &[])?; let construct = self.apply( if value { @@ -1728,18 +1736,19 @@ impl<'a> Writer<'_, 'a> { [] => self.boolean_pf(env, expr, true), [only] if direct => self.call_pf(binding, env, expr, only), [first, rest @ ..] => { - let (condition, then_branch, else_branch) = match expr { - Expr::Cond { - condition, - then_branch, - else_branch, - } => (condition.as_ref(), then_branch.as_ref(), else_branch.as_ref()), - _ => { - return Err( - "a validator was lowered without the test of a check".to_owned() - ); - } + let Expr::Cond { + condition, + then_branch, + else_branch, + } = expr + else { + return Err("a validator was lowered without the test of a check".to_owned()); }; + let (condition, then_branch, else_branch) = ( + condition.as_ref(), + then_branch.as_ref(), + else_branch.as_ref(), + ); let tested = self.call_pf(binding, env, condition, first)?; let continued = self.chain_pf(binding, env, then_branch, rest, direct)?; let refused = self.boolean_pf(env, else_branch, false)?; @@ -1933,37 +1942,46 @@ impl<'a> Writer<'_, 'a> { let mut layers: Vec<(usize, u64, &'a Expr, &'a Expr)> = Vec::new(); let mut cursor = &function.body; for (position, validator) in &checked { - match cursor { - Expr::Cond { - condition, - then_branch, - else_branch, - } => { - match condition.as_ref() { - Expr::Call { - function: callee, - operands, - } if callee == validator && operands.len() == 1 => {} - _ => { - return Err(format!( - "the entry was lowered without the call of the validator {validator}" - )); - } - } - layers.push((*position, *validator, then_branch.as_ref(), else_branch.as_ref())); - cursor = then_branch.as_ref(); - } - _ => return Err("the entry was lowered without a test of a validator".to_owned()), + let Expr::Cond { + condition, + then_branch, + else_branch, + } = cursor + else { + return Err("the entry was lowered without a test of a validator".to_owned()); + }; + let Expr::Call { + function: callee, + operands, + } = condition.as_ref() + else { + return Err("the entry tests something other than a validator's call".to_owned()); + }; + if callee != validator || operands.len() != 1 { + return Err(format!( + "the entry was lowered without the call of the validator {validator}" + )); } + layers.push(( + *position, + *validator, + then_branch.as_ref(), + else_branch.as_ref(), + )); + cursor = then_branch.as_ref(); } - let (shape, outcome_ty_program) = match cursor { - Expr::Build { - shape, - ty, - operands, - } if operands.len() == 1 => (*shape, ty), - _ => return Err("the entry was lowered without the root's call".to_owned()), + let Expr::Build { + shape, + ty: outcome_ty_program, + operands, + } = cursor + else { + return Err("the entry was lowered without the root's call".to_owned()); }; + if operands.len() != 1 { + return Err("the entry builds its result from another number of operands".to_owned()); + } + let shape = *shape; let mut argument_pfs = Vec::new(); for (position, number) in function.parameters.iter().enumerate() { argument_pfs.push(self.variable_pf(env, *number, encoded[position])?); @@ -2004,10 +2022,7 @@ impl<'a> Writer<'_, 'a> { let mut current = self.apply( "Conv.fits_eq", &[("f'", sig.fits_applied)], - &[ - Given::Proof(built.proof, built.ty), - Given::Term(equation), - ], + &[Given::Proof(built.proof, built.ty), Given::Term(equation)], )?; let some = self.ts("Value.some"); let mut fits_inside = sig.fits_applied; @@ -2025,7 +2040,8 @@ impl<'a> Writer<'_, 'a> { let value = self.b.lam_over("__c", EXPLICIT, bool, guard, on_value); let test = self.valid_at(*validator, x); // The test: the validator's relation at the parameter. - let argument = self.variable_pf(env, function.parameters[*position], encoded[*position])?; + let argument = + self.variable_pf(env, function.parameters[*position], encoded[*position])?; let list = self.operands(env, &[argument])?; let relation_head = self.vrel_const(*validator); let relation = self.b.app(relation_head, x); @@ -2082,7 +2098,11 @@ impl<'a> Writer<'_, 'a> { ("fn", function_term), ("env", env), ], - &[Given::Refl, Given::Refl, Given::Proof(current.proof, current.ty)], + &[ + Given::Refl, + Given::Refl, + Given::Proof(current.proof, current.ty), + ], )?; let run = self.apply( "run_of_funRel", @@ -2090,9 +2110,10 @@ impl<'a> Writer<'_, 'a> { &[Given::Proof(introduced.proof, introduced.ty)], )?; let run_conv = self.lp("RunConv"); - let statement = self - .b - .apps(run_conv, &[prog, entry_index, sig.arguments, denote_entry_at]); + let statement = self.b.apps( + run_conv, + &[prog, entry_index, sig.arguments, denote_entry_at], + ); let entry_value_term = self.lambdas(&sig.rel_binders, run.proof); let entry_ty = self.pis(&sig.rel_binders, statement); self.push_row("entry", RowKind::Theorem, entry_ty, entry_value_term)?; @@ -2102,14 +2123,8 @@ impl<'a> Writer<'_, 'a> { let some_obs = self.lp("someObs"); let accepted_obs = self.b.app(some_obs, denote_at); let hypothesis = self.b.free(); - let accepts_proof = self.entry_accepts_proof( - sig, - &checked, - &frees, - &tests, - &accepts_terms, - hypothesis, - )?; + let accepts_proof = + self.entry_accepts_proof(sig, &checked, &frees, &tests, &accepts_terms, hypothesis)?; let accepted = self.obs_eq(denote_entry_at, accepted_obs); let accepted_pi = self .b @@ -2126,14 +2141,8 @@ impl<'a> Writer<'_, 'a> { accepted_value, )?; let hypothesis = self.b.free(); - let refusal = self.entry_refuses_proof( - sig, - &checked, - &frees, - &tests, - &accepts_terms, - hypothesis, - )?; + let refusal = + self.entry_refuses_proof(sig, &checked, &frees, &tests, &accepts_terms, hypothesis)?; let not = self.b.konst("Not"); let refused_hypothesis = self.b.app(not, accepts_at); let value_wrap = self.lp("Obs.value"); @@ -2152,14 +2161,18 @@ impl<'a> Writer<'_, 'a> { /// The program's refusal: `build none`. fn refusal_pf(&mut self, env: Term, expr: &Expr) -> Result { - let (shape, ty) = match expr { - Expr::Build { - shape, - ty, - operands, - } if operands.is_empty() => (*shape, ty), - _ => return Err("the entry was lowered without its refusal".to_owned()), + let Expr::Build { + shape, + ty, + operands, + } = expr + else { + return Err("the entry was lowered without its refusal".to_owned()); }; + if !operands.is_empty() { + return Err("the entry's refusal builds from operands".to_owned()); + } + let shape = *shape; let empty = self.operands(env, &[])?; let construct = self.apply("construct_none", &[], &[])?; let shape = self.shape_term(shape); @@ -2253,8 +2266,7 @@ impl<'a> Writer<'_, 'a> { }; let viff = { let head = self.viff_const(*validator); - let at = self.b.app(head, frees[checked[index].0]); - at + self.b.app(head, frees[checked[index].0]) }; let decided = self.is_true(*test); let equation = { @@ -2273,10 +2285,7 @@ impl<'a> Writer<'_, 'a> { }; let rewrite = self.b.konst_at( "Eq.ndrec", - vec![ - CoreLevel::Zero, - CoreLevel::Succ(Box::new(CoreLevel::Zero)), - ], + vec![CoreLevel::Zero, CoreLevel::Succ(Box::new(CoreLevel::Zero))], ); proof = self .b @@ -2300,7 +2309,7 @@ impl<'a> Writer<'_, 'a> { hypothesis: Term, ) -> Result { let bool = self.bool_ty(); - let (tru, fls) = (self.tru(), self.fls()); + let fls = self.fls(); let none = self.ts("Value.none"); let wrap = self.lp("Obs.value"); let rhs = self.b.app(wrap, none); @@ -2360,7 +2369,9 @@ impl<'a> Writer<'_, 'a> { let refuted = self.b.app(not, accepted); let claim = self.b.arrow(refuted, goal); // The motive abstracts the variable at `index`. - let abstracted = self.b.lam_over("__b", EXPLICIT, bool, variables[index], claim); + let abstracted = self + .b + .lam_over("__b", EXPLICIT, bool, variables[index], claim); // `false`: the goal holds whatever follows. let mut at_false = callers.clone(); at_false[index] = fls; @@ -2430,7 +2441,8 @@ impl<'a> Writer<'_, 'a> { let invariant = invariants[index]; let holds = { let head = self.b.konst("Iff.mp"); - self.b.apps(head, &[decided, invariant, viff, equations[index]]) + self.b + .apps(head, &[decided, invariant, viff, equations[index]]) }; let intro = self.b.konst("And.intro"); inv_proof = self @@ -2453,7 +2465,9 @@ impl Writer<'_, '_> { _ => return Err("temporary".to_owned()), }; let lean = self.lean_ty(ty)?; - let instance = self.b.konst(&format!("{module}.LexLeanCollections.{instance}")); + let instance = self + .b + .konst(&format!("{module}.LexLeanCollections.{instance}")); let compare = self .b .konst(&format!("{module}.LexLeanCollections.Key.compare")); From e40874a1c54dd33a0bf20cffc2ec98e9c2772757 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 18:08:40 +0000 Subject: [PATCH 14/41] #89 stage 3: certificate A data: a definition a model construct elaborates to pins no axiom Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- .../src/production/certificate/data_a.rs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/lexlean/src/production/certificate/data_a.rs b/crates/lexlean/src/production/certificate/data_a.rs index 9f67a4504..021bdd8e2 100644 --- a/crates/lexlean/src/production/certificate/data_a.rs +++ b/crates/lexlean/src/production/certificate/data_a.rs @@ -197,6 +197,21 @@ impl<'g, 'a> Writer<'g, 'a> { for declaration in linked.semantic.lowered_declarations() { let mut axioms = declaration.axioms().to_vec(); axioms.sort(); + // A definition a model construct elaborates to carries the + // construct's declared set as an upper bound; the audit finds + // none for it, as it finds for any definition that proves + // nothing, so the exact set is empty. + let derived = matches!(declaration, crate::ir::semantic::SemanticDeclaration::Definition { .. }) + && !linked.semantic.declarations.iter().any(|source| { + source.name() == declaration.name() + && matches!( + source, + crate::ir::semantic::SemanticDeclaration::Definition { .. } + ) + }); + if derived { + axioms.clear(); + } declared.insert( format!("{}.{}", linked.lean_module, declaration.name()), axioms, @@ -363,7 +378,11 @@ impl<'g, 'a> Writer<'g, 'a> { } } for name in names { - out.extend(self.constant_axioms(&name)?); + let found = self.constant_axioms(&name)?; + if std::env::var("LEXLEAN_AXDBG").is_ok() && !found.is_empty() { + eprintln!("AXDBG {name} {found:?}"); + } + out.extend(found); } Ok(out) } From 82e461593aec7cd0611e6f10c75602f0fa3a4fcb Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 18:29:01 +0000 Subject: [PATCH 15/41] #89 stage 3: certificate A data: the group's implementation records name their axiom-bearing callees so Lean's cyclic axiom cache is order-independent --- .../production/certificate/data_a/boundary.rs | 78 ++++++++++++++----- 1 file changed, 57 insertions(+), 21 deletions(-) diff --git a/crates/lexlean/src/production/certificate/data_a/boundary.rs b/crates/lexlean/src/production/certificate/data_a/boundary.rs index cc3010b07..eb0054b1a 100644 --- a/crates/lexlean/src/production/certificate/data_a/boundary.rs +++ b/crates/lexlean/src/production/certificate/data_a/boundary.rs @@ -354,11 +354,10 @@ impl<'a> Writer<'_, 'a> { /// The four rows of a validator whose type is not in a recursive group. fn plain_validator(&mut self, boundary: &Boundary, index: u64) -> Result<(), String> { let validator = boundary.get(index)?.clone(); - if let Validation::Document { fields } = &validator.kind { - let fields = fields.clone(); - return self.plain_document(boundary, &validator, &fields); + if let Validation::Document { fields: _ } = &validator.kind { + return self.plain_document(boundary, &validator); } - let [valid, inv, viff, vrel] = self.library_validator(boundary, &validator)?; + let [valid, inv, viff, vrel] = self.library_validator(&validator)?; self.push_family(Family::Valid, &validator, valid)?; self.push_family(Family::Inv, &validator, inv)?; self.push_family(Family::Viff, &validator, viff)?; @@ -384,11 +383,7 @@ impl<'a> Writer<'_, 'a> { /// `(valid, inv, viff, vrel)` of a container or trivial validator, by the /// library's definitions, propositions, decision theorems, and templates. #[allow(clippy::too_many_lines)] - fn library_validator( - &mut self, - boundary: &Boundary, - validator: &Validator, - ) -> Result<[Term; 4], String> { + fn library_validator(&mut self, validator: &Validator) -> Result<[Term; 4], String> { let site = Site { module: validator.module.clone(), substitution: BTreeMap::new(), @@ -716,7 +711,6 @@ impl<'a> Writer<'_, 'a> { let vrel = self .b .lam_over("__v", EXPLICIT, validator.lean, x, relation.proof); - let _ = boundary; Ok([valid, inv, viff, vrel]) } @@ -845,13 +839,7 @@ impl<'a> Writer<'_, 'a> { } /// A document type's validator outside any recursion: by its eliminator. - fn plain_document( - &mut self, - boundary: &Boundary, - validator: &Validator, - fields: &[Vec>], - ) -> Result<(), String> { - let _ = fields; + fn plain_document(&mut self, boundary: &Boundary, validator: &Validator) -> Result<(), String> { let constructors = self.plain_constructors(&validator.ty)?; let ctors = self.constructor_specs(validator, &constructors)?; let group = Group { @@ -972,8 +960,40 @@ impl<'a> Writer<'_, 'a> { Ok(()) } + /// The validators outside a group that its members call, which bring + /// axioms: the group's records name them all in each record (see + /// [`Self::unsafe_records`]). + fn external_axiom_callees(&mut self, group: &Group, order: &[(u64, usize)]) -> Vec { + let mut callees = BTreeSet::new(); + for (_, position) in order { + for ctor in &group.positions[*position].ctors { + for check in &ctor.checks { + if ctor.recursive[check.field].is_none() { + callees.insert(check.validator); + } + } + } + } + callees + .into_iter() + .filter(|callee| { + let name = format!("{}.__valid_{callee}", self.name); + self.constant_axioms(&name) + .is_ok_and(|axioms| !axioms.is_empty()) + }) + .collect() + } + /// The implementation records of a group's `__valid_k`: direct recursion /// by the eliminator of each type, calling the records of the others. + /// + /// The records call one another, and Lean computes the axioms of a + /// definition in a cycle with a cache that a call still in progress cuts + /// short: which record sees the axioms of a callee that only another + /// record names depends on the order the declarations are visited in. So + /// that every record's axioms are the same whatever that order is, each + /// names (in a `let` the code generator drops) every callee outside the + /// group that brings axioms, so each record sees them directly. fn unsafe_records( &mut self, boundary: &Boundary, @@ -981,6 +1001,20 @@ impl<'a> Writer<'_, 'a> { order: &[(u64, usize)], ) -> Result<(), String> { let bool = self.bool_ty(); + let external = self.external_axiom_callees(group, order); + let mut ballast: Option<(Term, Term)> = None; + for callee in external.iter().rev() { + let validator = boundary.get(*callee)?; + let ty = self.b.arrow(validator.lean, bool); + let constant = self.valid_const(*callee); + ballast = Some(match ballast { + None => (ty, constant), + Some((rest_ty, rest)) => ( + self.b.pair_ty(ty, rest_ty), + self.b.pair(ty, rest_ty, constant, rest), + ), + }); + } let mut names = Vec::new(); for (member, position) in order { let validator = boundary.get(*member)?.clone(); @@ -1001,7 +1035,12 @@ impl<'a> Writer<'_, 'a> { let lifted = self.b.lift(bool, 1, 0); self.b.lam("_", EXPLICIT, validator.lean, lifted) }; - let cases = self.cases_on(&validator.ty, free, motive, one, &minors)?; + let mut cases = self.cases_on(&validator.ty, free, motive, one, &minors)?; + if let Some((ballast_ty, ballast_value)) = ballast { + cases = self + .b + .let_node("_callees", ballast_ty, ballast_value, cases, false); + } let value = self .b .lam_over("__v", EXPLICIT, validator.lean, free, cases); @@ -2239,7 +2278,6 @@ impl<'a> Writer<'_, 'a> { CoreLevel::Zero, &[overflow_case, value_case], )?; - let _ = (checked, obs); let mut proof = base; let mut callers = trues; // The invariants' conjunction, taken apart from the first. @@ -2375,7 +2413,6 @@ impl<'a> Writer<'_, 'a> { // `false`: the goal holds whatever follows. let mut at_false = callers.clone(); at_false[index] = fls; - let false_goal = self.entry_goal(sig, &at_false, rhs); let false_accepted = all_true(self, &at_false); let false_refuted = self.b.app(not, false_accepted); let on_false = { @@ -2383,7 +2420,6 @@ impl<'a> Writer<'_, 'a> { let value_wrap = self.lp("Obs.value"); let value = self.b.app(value_wrap, none); let refl = self.refl(obs, value); - let _ = false_goal; self.b.lam_over("__n", EXPLICIT, false_refuted, free, refl) }; let cases = self.cases_on( From d9c6655e01b14a9796549a07924efcfe580a057e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 18:53:45 +0000 Subject: [PATCH 16/41] #89 stage 3: the statement oracle compares by the kernel's definitional equality Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- crates/conformance/src/preservation.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/crates/conformance/src/preservation.rs b/crates/conformance/src/preservation.rs index e24c6e578..4726bed08 100644 --- a/crates/conformance/src/preservation.rs +++ b/crates/conformance/src/preservation.rs @@ -2941,7 +2941,7 @@ mod dev { /// certificate A of the same root with its namespace renamed to /// `.Old`, and an oracle module compares, for every row both /// hold, the type of the data's row, its own names read as the old - /// module's, with the type of the old row by `Lean.Meta.isDefEq`. The + /// module's, with the type of the old row by the kernel's definitional equality (`Lean.Kernel.isDefEq`, which unfolds a `match` to its eliminator as the checker does). The /// bodies of the definitions are compared and counted, not required: a /// recursive width predicate is a different, equally proved, definition. #[test] @@ -3025,7 +3025,8 @@ mod dev { &format!("namespace {module}\n"), &format!("namespace {old}\n"), ) - .replace(&format!("end {module}"), &format!("end {old}")); + .replace(&format!("end {module}"), &format!("end {old}")) + .replace(&format!("_root_.{module}."), &format!("_root_.{old}.")); assert_ne!(renamed, committed, "R{index}: the namespace is renamed"); let output = compile_text(scratch.path(), &old, &renamed); assert!( @@ -3048,10 +3049,18 @@ mod dev { .iter() .map(|row| format!("(`{module}.{row}, `{old}.{row})")) .collect(); + let show = if std::env::var("LEXLEAN_ORACLE_SHOW").is_ok() { + format!( + " logInfo m!\"NEW {{swapRoot `{module} `{old} n.type}}\\nOLD {{o.type}}\"\n" + ) + } else { + String::new() + }; let oracle = format!( "import {module}\nimport {old}\nimport Lean\nopen Lean Meta\n\ def swapRoot (new old : Name) (e : Expr) : Expr :=\n e.replace fun\n | .const n us =>\n if new.isPrefixOf n && !old.isPrefixOf n then some (.const (n.replacePrefix new old) us) else none\n | _ => none\n\ - run_cmd Lean.Elab.Command.liftTermElabM do\n let pairs : List (Name × Name) := [{}]\n let mut types : Nat := 0\n let mut same : Nat := 0\n let mut total : Nat := 0\n for (new, old) in pairs do\n let env ← getEnv\n let some n := env.find? new | throwError \"{{new}}: not in the environment\"\n let some o := env.find? old | throwError \"{{old}}: the committed certificate lacks the row\"\n unless (← isDefEq (swapRoot `{module} `{old} n.type) o.type) do\n throwError \"{{new}}: the statement differs from {{old}}\"\n types := types + 1\n if let (some a, some b) := (n.value?, o.value?) then\n total := total + 1\n if (← isDefEq (swapRoot `{module} `{old} a) b) then same := same + 1\n logInfo m!\"SP-15 oracle: {{types}} statements equal; bodies {{same}} of {{total}}\"\n", + def kdef (a b : Expr) : MetaM Bool := do\n ofExceptKernelException (Kernel.isDefEq (← getEnv) {{}} a b)\n\ + run_cmd Lean.Elab.Command.liftTermElabM do\n let pairs : List (Name × Name) := [{}]\n let mut types : Nat := 0\n let mut same : Nat := 0\n let mut total : Nat := 0\n let mut bad : Nat := 0\n for (new, old) in pairs do\n let env ← getEnv\n let some n := env.find? new | throwError \"{{new}}: not in the environment\"\n let some o := env.find? old | throwError \"{{old}}: the committed certificate lacks the row\"\n if (← kdef (swapRoot `{module} `{old} n.type) o.type) then\n types := types + 1\n else\n logWarning m!\"{{new}}: the statement differs from {{old}}\"\n bad := bad + 1\n{show} if let (some a, some b) := (n.value?, o.value?) then\n total := total + 1\n if (← kdef (swapRoot `{module} `{old} a) b) then same := same + 1\n if bad > 0 then throwError \"{{bad}} statements differ\"\n logInfo m!\"SP-15 oracle: {{types}} statements equal; bodies {{same}} of {{total}}\"\n", pairs.join(", ") ); let name = format!("{module}.Oracle"); @@ -3060,7 +3069,7 @@ mod dev { assert!( output.status.success(), "R{index}: the statements of {module} are the committed certificate's:\n{}", - text.lines().take(20).collect::>().join("\n") + text.lines().take(4000).collect::>().join("\n") ); eprintln!("R{index} {module}: {}", text.trim()); compared += rows.len(); From 07590c8fed18f3474979dda10f835f4ef28d2c4f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 19:02:03 +0000 Subject: [PATCH 17/41] #89 stage 3: the record of the statement oracle over the committed certificates A Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- .../preservation-certificates/A-STATEMENTS.md | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 tests/preservation-certificates/A-STATEMENTS.md diff --git a/tests/preservation-certificates/A-STATEMENTS.md b/tests/preservation-certificates/A-STATEMENTS.md new file mode 100644 index 000000000..37b964b42 --- /dev/null +++ b/tests/preservation-certificates/A-STATEMENTS.md @@ -0,0 +1,117 @@ +# Certificate A as data: the statements are the text's + +Certificate A of every production root was Lean text written by +`production/certificate.rs`. It is now kernel-term data: a native core module of +schema `lexlean/core-module/2` the compiler builds and the one core backend +renders (SPEC.md §17.17, `SP-15`; stage 3 of #89). This record is the evidence +that the change kept every statement: for each row of each data certificate, the +kernel term it states is definitionally the kernel term the committed text +certificate elaborated to, under the pinned Lean 4.32.1. + +## How it was checked + +At the branch commit `d9c6655e`, before the verify records were regenerated, the +committed records of the four corpora still held the text certificates A +(`examples//expected/verify/preserve/LexLeanPreserve/C/R.lean`). +For each corpus the oracle (`dev_oracle_a` in `crates/conformance/src/preservation.rs`): + +1. built every root's certificate A as data, staged it with the environment and + the project's generated modules, and compiled and replayed it with the pinned + Lean (`preservation::check`); +2. compiled the committed text certificate A of the same root beside it, its + namespace renamed from `` to `.Old` (the `namespace` and `end` + lines, and the absolute `_root_..` references); +3. compiled an oracle module importing both which, for every row of the data + certificate, compares the type of `.` (its own names read as the + old module's) with the type of `.Old.` by the kernel's + definitional equality (`Lean.Kernel.isDefEq`), and fails on the first pair + that differs. The kernel's equality is used because it is the one verification + relies on: `Lean.Meta.isDefEq` does not unfold a `match` to the eliminator the + data writes, so it refuses `match l with | [] => a | x :: _ => b` against + `List.casesOn l a fun x _ => b`, which the kernel identifies. +4. compared, and counted without requiring, the bodies of the definitions: a + recursive width predicate is a different, separately proved definition. + +Command: `LEXLEAN_EXAMPLE= cargo test -p repo-conformance --lib dev_oracle_a -- --ignored --nocapture` +on Linux x86-64 with the pinned toolchain. + +## Result + +Every statement of every certificate A is the statement the text stated: +57 certificates A, 1326 rows, every pair definitionally equal; definition +bodies equal 696 of 739. + +| corpus | certificates A | rows | definition bodies equal | +| --- | ---: | ---: | ---: | +| `production` | 6 | 45 | 25 of 27 | +| `production-coverage` | 34 | 479 | 246 of 287 | +| `models` | 9 | 243 | 129 of 129 | +| `reasoning` | 8 | 559 | 296 of 296 | + +Every certificate, by root: + +| corpus | root | rows equal | definition bodies equal | +| --- | --- | ---: | ---: | +| `production` | `C7b8829e2cc52192864282f3d66903769.R0` | 5 | 3 of 3 | +| `production` | `C7b8829e2cc52192864282f3d66903769.R1` | 8 | 5 of 5 | +| `production` | `C7b8829e2cc52192864282f3d66903769.R2` | 9 | 5 of 5 | +| `production` | `C7b8829e2cc52192864282f3d66903769.R3` | 8 | 4 of 5 | +| `production` | `C7b8829e2cc52192864282f3d66903769.R4` | 8 | 4 of 5 | +| `production` | `C7b8829e2cc52192864282f3d66903769.R5` | 7 | 4 of 4 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R0` | 105 | 44 of 62 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R1` | 20 | 11 of 11 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R2` | 22 | 12 of 12 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R3` | 16 | 9 of 9 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R4` | 20 | 11 of 11 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R5` | 9 | 5 of 5 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R6` | 20 | 11 of 11 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R7` | 20 | 11 of 11 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R8` | 40 | 21 of 21 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R9` | 5 | 3 of 3 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R10` | 5 | 3 of 3 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R11` | 5 | 3 of 3 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R12` | 8 | 5 of 5 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R13` | 10 | 5 of 7 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R14` | 12 | 8 of 8 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R15` | 5 | 3 of 3 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R16` | 5 | 3 of 3 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R17` | 5 | 3 of 3 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R18` | 13 | 7 of 7 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R19` | 14 | 6 of 9 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R20` | 11 | 5 of 7 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R21` | 5 | 3 of 3 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R22` | 5 | 3 of 3 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R23` | 5 | 3 of 3 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R24` | 5 | 3 of 3 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R25` | 5 | 3 of 3 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R26` | 5 | 3 of 3 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R27` | 5 | 3 of 3 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R28` | 5 | 3 of 3 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R29` | 5 | 3 of 3 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R30` | 16 | 8 of 12 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R31` | 21 | 10 of 16 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R32` | 14 | 6 of 9 | +| `production-coverage` | `C6cddd56cf041af764eeefdf9e95c897e.R33` | 13 | 6 of 9 | +| `models` | `Ccca462bfe8cabc3365338af96b591362.R0` | 46 | 24 of 24 | +| `models` | `Ccca462bfe8cabc3365338af96b591362.R1` | 50 | 26 of 26 | +| `models` | `Ccca462bfe8cabc3365338af96b591362.R2` | 43 | 23 of 23 | +| `models` | `Ccca462bfe8cabc3365338af96b591362.R3` | 26 | 14 of 14 | +| `models` | `Ccca462bfe8cabc3365338af96b591362.R4` | 14 | 8 of 8 | +| `models` | `Ccca462bfe8cabc3365338af96b591362.R5` | 13 | 7 of 7 | +| `models` | `Ccca462bfe8cabc3365338af96b591362.R6` | 15 | 8 of 8 | +| `models` | `Ccca462bfe8cabc3365338af96b591362.R7` | 13 | 7 of 7 | +| `models` | `Ccca462bfe8cabc3365338af96b591362.R8` | 23 | 12 of 12 | +| `reasoning` | `C6d3e375f76cbc573b09fef98086539e0.R0` | 86 | 45 of 45 | +| `reasoning` | `C6d3e375f76cbc573b09fef98086539e0.R1` | 90 | 47 of 47 | +| `reasoning` | `C6d3e375f76cbc573b09fef98086539e0.R2` | 92 | 49 of 49 | +| `reasoning` | `C6d3e375f76cbc573b09fef98086539e0.R3` | 75 | 40 of 40 | +| `reasoning` | `C6d3e375f76cbc573b09fef98086539e0.R4` | 29 | 16 of 16 | +| `reasoning` | `C6d3e375f76cbc573b09fef98086539e0.R5` | 84 | 44 of 44 | +| `reasoning` | `C6d3e375f76cbc573b09fef98086539e0.R6` | 61 | 33 of 33 | +| `reasoning` | `C6d3e375f76cbc573b09fef98086539e0.R7` | 42 | 22 of 22 | + +What changed is the proof term (now written with every argument, by the data +generator), the form of the module (kernel data rendered by the decoder, in place +of Lean source text), and the form of a recursive width predicate and relation +theorem (the recursor, or `WellFounded.fix`, in place of a compiled recursive +definition); what a verification establishes of a root did not. From cd7d3d29b70343c9ee49d5188addcc7993d63c54 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 19:10:55 +0000 Subject: [PATCH 18/41] #89 stage 3: the text generator of certificate A is deleted from the production compiler Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- crates/lexlean/src/production/certificate.rs | 7547 +++-------------- .../src/production/certificate/data_a.rs | 18 +- 2 files changed, 1315 insertions(+), 6250 deletions(-) diff --git a/crates/lexlean/src/production/certificate.rs b/crates/lexlean/src/production/certificate.rs index 22a487101..37b6cc186 100644 --- a/crates/lexlean/src/production/certificate.rs +++ b/crates/lexlean/src/production/certificate.rs @@ -72,8 +72,8 @@ pub const LIBRARY_MODULES: [&str; 7] = [ const SYNTAX: &str = "LexLeanTarget.TargetSyntax"; const SEMANTICS: &str = "LexLeanTarget.TargetSemantics"; -const SEMANTICS_MODULE: &str = SEMANTICS; const NAT_BOUND: &str = "18446744073709551616"; +const SEMANTICS_MODULE: &str = SEMANTICS; /// One generated certificate module. #[derive(Debug, Clone, PartialEq, Eq)] @@ -210,14 +210,6 @@ fn internal(reason: impl std::fmt::Display) -> Diagnostic { Diagnostic::new(code!("LLI9001"), format!("phase preservation: {reason}")) } -fn lib(name: &str) -> String { - format!("{LIBRARY}.{name}") -} - -fn value_ctor(name: &str) -> String { - format!("{SYNTAX}.Value.{name}") -} - /// A local of a certificate scope: its source binding and, for a /// function-typed local, the binders of its companion: the width predicate, /// function index, captured values, and relation hypothesis of the closure @@ -315,30 +307,6 @@ impl Ctx { } } -/// A proof that a term's lowering converges to the relation of its fits -/// predicate and denotation, and that predicate. -#[derive(Clone)] -struct Proof { - proof: String, - fits: String, -} - -/// A function-typed argument with its companions. -struct FunctionArgument { - /// The source value. - value: String, - /// Its fits function. - fits: String, - /// Its closure's function index. - index: String, - /// Its closure's encoded captures. - captures: String, - /// Its relation, a function of its parameters. - relation: String, - /// The proof of the closure expression. - proof: Proof, -} - /// How a function's relation theorem recurses. #[derive(Clone)] enum Recursion { @@ -606,63 +574,6 @@ pub fn certificate( }) } -/// The namespaces of the shipped library, the target, the certificates, and -/// the runtime, which a certificate names from outside any scope of the -/// source. -const GLOBAL_ROOTS: [&str; 6] = [ - "LexLeanPreservation", - "LexLeanTarget", - "LexLeanPreserve", - "LexLeanRuntime", - "LexLeanCollections", - "LexLeanAudit", -]; - -/// `text` with every reference to a name of [`GLOBAL_ROOTS`] written from the -/// root, `_root_.LexLeanPreservation.conv_var`: a parameter named like a -/// namespace would otherwise turn the first segment of such a name into a -/// field access of itself. Declarations, `import`, `open`, `namespace`, and -/// `end` lines are left as they are. -fn from_the_root(text: &str) -> Result { - use super::preserve::{lex, LexemeKind}; - let lexemes = lex(text)?; - let chars: Vec = text.chars().collect(); - let mut out = String::with_capacity(text.len() + text.len() / 16); - let mut copied = 0usize; - let mut skipped_line = usize::MAX; - for lexeme in &lexemes { - let (opens, reaches) = match &lexeme.kind { - LexemeKind::Ident(segments) => ( - segments.len() == 1 - && !segments[0].quoted - && ["import", "open", "namespace", "end"].contains(&segments[0].text.as_str()), - segments.len() > 1 - && !segments[0].quoted - && GLOBAL_ROOTS.contains(&segments[0].text.as_str()), - ), - LexemeKind::Number - | LexemeKind::Literal - | LexemeKind::Command(_) - | LexemeKind::Symbol(_) => (false, false), - }; - if lexeme.first_on_line { - skipped_line = if opens { lexeme.line } else { usize::MAX }; - } - let after_dot = lexeme.start > 0 && chars[lexeme.start - 1] == '.'; - if reaches && !after_dot && lexeme.line != skipped_line { - out.extend(&chars[copied..lexeme.start]); - out.push_str("_root_."); - copied = lexeme.start; - } - } - out.extend(&chars[copied..chars.len()]); - Ok(out) -} - -/// `true` as a certificate writes it where a source name could be in scope: a -/// parameter may be named `true`, which would capture the bare word. -const TRUE: &str = "Bool.true"; - /// The Lean spelling of a semantic name in a certificate: the backend's, and /// in addition quoted when a segment is spelled like a token the token audit /// forbids (`kernel`, `extern`), so that a user's name is an identifier to the @@ -1157,54 +1068,6 @@ fn function_type(ty: &SemanticType) -> Option<(&[SemanticType], &SemanticType)> } } -/// A conjunction of fits predicates, in evaluation order. -fn and(left: &str, right: &str) -> String { - format!("({left} && {right})") -} - -/// The proof and fits of an operand list. -fn operands(proofs: Vec) -> Proof { - proofs.into_iter().rev().fold( - Proof { - proof: lib("convL_nil"), - fits: TRUE.to_owned(), - }, - |rest, head| Proof { - proof: format!("({} {} {})", lib("convL_cons"), head.proof, rest.proof), - fits: and(&head.fits, &rest.fits), - }, - ) -} - -/// A built value: `conv_build` over the operands with `lemma` for the -/// constructor, whose fits is `fits`. -fn built(operands_proof: Proof, lemma: &str, fits: &str) -> Proof { - Proof { - proof: format!("({} {} {})", lib("conv_build"), operands_proof.proof, lemma), - fits: and(&operands_proof.fits, fits), - } -} - -/// The width predicate a primitive result contributes. -enum Width { - /// The result is always representable. - Exact, - /// A natural-number result realized in 64 bits. - Nat, - /// A mathematical-integer result realized in 64 bits. - Int, - /// A library predicate applied to the operands. - Predicate(&'static str), -} - -/// A primitive's relation lemma, the encoders it takes before its operands, -/// and the width predicate its result contributes. -struct PrimRule { - lemma: String, - prefix: Vec, - width: Width, -} - impl Gen<'_> { /// The locals a branch binds, with their closed and generic types. fn branch_locals( @@ -1247,568 +1110,7 @@ impl Gen<'_> { } } -impl Gen<'_> { - fn lean_module(&self, module: &str) -> Result { - self.source.lean_module(module).map(str::to_owned) - } - - /// A member reference read at `site`, fully qualified. - /// The local irreducibility under which a relation of `body` is - /// elaborated: every definition the body calls is related by its own - /// theorem, so unfolding one is never needed, and an elaborator that - /// unfolds one to compare two matches on its value would evaluate it, - /// which for a definition over large constant data costs minutes. - fn sealed(&self, body: &SemanticTerm, site: &Site, own: &str) -> Result { - let mut names = BTreeSet::new(); - for member in called_members(body) { - let name = self.member(&member, site)?; - if name != own { - names.insert(name); - } - } - if names.is_empty() { - return Ok(String::new()); - } - Ok(format!( - "attribute [local irreducible] {} in\n", - names.into_iter().collect::>().join(" ") - )) - } - - fn member(&self, member: &MemberRef, site: &Site) -> Result { - // A violation is the pair of Booleans it lowers to (§17.12 rule 9). - match crate::backend::semantic::violation_pattern(member) { - Some(pair) => { - return Ok(pair.replace("true", TRUE).replace("false", "Bool.false")); - } - None => {} - } - if member.module.is_none() { - if member.name == "Result.error" { - return Ok("Except.error".to_owned()); - } - if member.name == "Result.ok" { - return Ok("Except.ok".to_owned()); - } - let owner = member.name.rsplit_once('.').map(|(owner, _)| owner); - let builtin = BUILTIN_OWNERS.iter().any(|(name, _)| Some(*name) == owner); - if builtin { - return Ok(identifier(&member.name)); - } - } - let module = Source::module_of(member, site); - Ok(identifier(&format!( - "{}.{}", - self.lean_module(&module)?, - member.name - ))) - } - - /// The Lean spelling of a closed type. - fn ty(&self, ty: &SemanticType) -> Result { - self.render_type(ty, false) - } - - /// The Lean spelling of a type; a generic one may name the type - /// parameters a wrapper binds. - fn render_type(&self, ty: &SemanticType, generic: bool) -> Result { - lean_type_text(&self.source, ty, generic) - } - - /// The encoder of a closed first-order type into target values. - fn enc(&self, ty: &SemanticType) -> Result { - match self.nested.get(&self.source.type_text(ty)) { - Some(nested) => Ok(nested_enc(nested)), - None => self.plain_enc(ty), - } - } - - /// The encoder of a type with no nested auxiliary encoder. - fn plain_enc(&self, ty: &SemanticType) -> Result { - Ok(match ty { - SemanticType::Nat => value_ctor("nat"), - SemanticType::Bool => value_ctor("bool"), - SemanticType::Int => value_ctor("int"), - SemanticType::Int8 => value_ctor("i8"), - SemanticType::Int16 => value_ctor("i16"), - SemanticType::Int32 => value_ctor("i32"), - SemanticType::Int64 => value_ctor("i64"), - SemanticType::UInt8 => value_ctor("u8"), - SemanticType::UInt16 => value_ctor("u16"), - SemanticType::UInt32 => value_ctor("u32"), - SemanticType::UInt64 => value_ctor("u64"), - SemanticType::String => value_ctor("string"), - SemanticType::Bytes => value_ctor("bytes"), - SemanticType::Unit => lib("encUnit"), - SemanticType::Ordering => lib("encOrdering"), - SemanticType::ContractViolation | SemanticType::ReasoningFailure => format!( - "({} {} {})", - lib("encPair"), - value_ctor("bool"), - value_ctor("bool") - ), - SemanticType::Option { value } => { - format!("({} {})", lib("encOption"), self.enc(value)?) - } - SemanticType::Result { ok, error } => format!( - "({} {} {})", - lib("encExcept"), - self.enc(error)?, - self.enc(ok)? - ), - SemanticType::List { element } | SemanticType::Set { element } => { - format!("({} {})", lib("encList"), self.enc(element)?) - } - SemanticType::Product { left, right } => format!( - "({} {} {})", - lib("encPair"), - self.enc(left)?, - self.enc(right)? - ), - SemanticType::Map { key, value } => format!( - "({} ({} {} {}))", - lib("encList"), - lib("encPair"), - self.enc(key)?, - self.enc(value)? - ), - SemanticType::Named { - member: _, - arguments: _, - } => { - let text = self.source.type_text(ty); - let index = self - .adts - .get(&text) - .ok_or_else(|| format!("`{text}` has no ADT in the layout"))?; - format!("__enc_{index}") - } - SemanticType::Function { - parameters: _, - result: _, - } - | SemanticType::Type - | SemanticType::Prop - | SemanticType::Parameter { name: _ } => { - return Err(format!( - "`{}` has no value encoding", - self.source.type_text(ty) - )); - } - }) - } - - /// The encoded value of a first-order local, or the closure value of a - /// function-typed one. - fn encoded_local(&self, local: &CLocal) -> Result { - match &local.companion { - Some(companion) => Ok(format!( - "({} {} {})", - value_ctor("closure"), - companion.index, - companion.captures - )), - None => Ok(format!( - "({} {})", - self.enc(&local.local.ty)?, - identifier(&local.local.name) - )), - } - } - - fn runtime(&self, site: &Site, namespace: &str, operation: &str) -> Result { - Ok(format!( - "{}.{namespace}.{operation}", - self.lean_module(&site.module)? - )) - } - - /// A branch pattern at `site`. - fn pattern(&self, branch: &SemanticBranch, site: &Site) -> Result { - let mut out = self.member(&branch.constructor, site)?; - for binder in &branch.binders { - out.push(' '); - out.push_str(&identifier(binder)); - } - Ok(out) - } - - fn src_terms(&self, terms: &[SemanticTerm], ctx: &Ctx) -> Result { - let mut out = String::new(); - for term in terms { - out.push_str(" ("); - out.push_str(&self.src(term, ctx)?); - out.push(')'); - } - Ok(out) - } - - /// The Lean denotation of a source term, as the generated module states - /// it but fully qualified. - #[allow(clippy::too_many_lines)] - fn src(&self, term: &SemanticTerm, ctx: &Ctx) -> Result { - let site = &ctx.site; - let binary = |operator: &str, left: &SemanticTerm, right: &SemanticTerm| { - Ok::(format!( - "({} {operator} {})", - self.src(left, ctx)?, - self.src(right, ctx)? - )) - }; - Ok(match term { - SemanticTerm::Var { name } => identifier(name), - SemanticTerm::Nat { value } => format!("({value} : Nat)"), - SemanticTerm::Integer { - representation, - value, - } => format!("({value} : {})", integer_lean(*representation)), - SemanticTerm::String { value } => string_literal(value), - SemanticTerm::Bytes { hex } => format!("({})", bytes_literal(hex)?), - SemanticTerm::Primitive { - operation, - arguments, - result, - } => { - let result = self.source.close(result, site); - let specialization = match operation { - SemanticPrimitive::CheckedAdd => Some("checkedAddInt64"), - SemanticPrimitive::CheckedSubtract => Some("checkedSubtractInt64"), - SemanticPrimitive::CheckedMultiply => Some("checkedMultiplyInt64"), - SemanticPrimitive::CheckedNegate => Some("checkedNegateInt64"), - SemanticPrimitive::CheckedQuotient => Some("checkedQuotientInt64"), - SemanticPrimitive::Subtract - | SemanticPrimitive::Multiply - | SemanticPrimitive::Quotient - | SemanticPrimitive::Remainder - | SemanticPrimitive::Negate - | SemanticPrimitive::CheckedConvert - | SemanticPrimitive::BitAnd - | SemanticPrimitive::BitOr - | SemanticPrimitive::BitXor - | SemanticPrimitive::BitNot - | SemanticPrimitive::ShiftLeft - | SemanticPrimitive::ShiftRight - | SemanticPrimitive::Append - | SemanticPrimitive::Length - | SemanticPrimitive::Index - | SemanticPrimitive::Slice - | SemanticPrimitive::Utf8Encode - | SemanticPrimitive::Utf8Decode - | SemanticPrimitive::CompareBytes - | SemanticPrimitive::Equal - | SemanticPrimitive::SplitExact - | SemanticPrimitive::Join - | SemanticPrimitive::ParseDecimal - | SemanticPrimitive::FormatDecimal - | SemanticPrimitive::MapInsert - | SemanticPrimitive::MapRemove - | SemanticPrimitive::MapLookup - | SemanticPrimitive::MapContains - | SemanticPrimitive::MapSize - | SemanticPrimitive::MapKeys - | SemanticPrimitive::MapValues - | SemanticPrimitive::MapEntries - | SemanticPrimitive::MapFold - | SemanticPrimitive::SetInsert - | SemanticPrimitive::SetRemove - | SemanticPrimitive::SetContains - | SemanticPrimitive::SetSize - | SemanticPrimitive::SetElements - | SemanticPrimitive::SetUnion - | SemanticPrimitive::SetIntersection - | SemanticPrimitive::SetDifference - | SemanticPrimitive::SetFold - | SemanticPrimitive::ListFold - | SemanticPrimitive::Iterate - | SemanticPrimitive::IterateUntil - | SemanticPrimitive::GraphSuccessors - | SemanticPrimitive::GraphReachable - | SemanticPrimitive::GraphTopological - | SemanticPrimitive::LessThan => None, - }; - let int64 = SemanticType::Option { - value: Box::new(SemanticType::Int64), - }; - let (namespace, name) = match specialization { - Some(name) if result == int64 => ("LexLeanRuntime", name), - Some(_) | None => primitive_name(*operation), - }; - format!( - "({}{} : {})", - self.runtime(site, namespace, name)?, - self.src_terms(arguments, ctx)?, - self.ty(&result)? - ) - } - SemanticTerm::Bool { value } => if *value { TRUE } else { "Bool.false" }.to_owned(), - SemanticTerm::Unit => "()".to_owned(), - SemanticTerm::Nil { element } => format!( - "([] : List {})", - self.ty(&self.source.close(element, site))? - ), - SemanticTerm::Cons { head, tail } => { - format!("({} :: {})", self.src(head, ctx)?, self.src(tail, ctx)?) - } - SemanticTerm::Record { - r#type: _, - type_arguments: _, - fields, - } => { - let ty = self.source.infer(term, &ctx.sources(), site)?; - format!( - "({{ {} }} : {})", - fields - .iter() - .map(|SemanticAssignment { field, value }| { - Ok::(format!( - "{} := {}", - identifier(field), - self.src(value, ctx)? - )) - }) - .collect::, _>>()? - .join(", "), - self.ty(&ty)? - ) - } - SemanticTerm::Constructor { - constructor, - type_arguments: _, - arguments, - } => { - let ty = self.source.infer(term, &ctx.sources(), site)?; - format!( - "({}{} : {})", - self.member(constructor, site)?, - self.src_terms(arguments, ctx)?, - self.ty(&ty)? - ) - } - SemanticTerm::InstanceValue { - class: _, - arguments: _, - resolved, - } => self.member(resolved, site)?, - SemanticTerm::Project { value, field } => { - format!("({}).{}", self.src(value, ctx)?, identifier(field)) - } - SemanticTerm::Call { - function, - type_arguments, - arguments, - } => { - let mut out = format!("({}", self.member(function, site)?); - for argument in type_arguments { - out.push(' '); - out.push_str(&self.ty(&self.source.close(argument, site))?); - } - out.push_str(&self.src_terms(arguments, ctx)?); - out.push(')'); - out - } - SemanticTerm::If { - condition, - then_value, - else_value, - } => match ctx.hypotheses.get(&(std::ptr::from_ref(term) as usize)) { - Some(index) => format!( - "(match (generalizing := false) {} : {} with | true => {} | false => {})", - hypothesis(*index), - self.src(condition, ctx)?, - self.src(then_value, ctx)?, - self.src(else_value, ctx)? - ), - None => format!( - "(if {} then {} else {})", - self.src(condition, ctx)?, - self.src(then_value, ctx)?, - self.src(else_value, ctx)? - ), - }, - SemanticTerm::Match { - scrutinee, - branches, - } => { - let mut alternatives = Vec::new(); - for branch in branches { - let inner = ctx.with(self.branch_locals(branch, scrutinee, ctx)?); - alternatives.push(self.src(&branch.body, &inner)?); - } - let result = self.ty(&self.source.infer(term, &ctx.sources(), site)?)?; - let hypothesis = ctx - .hypotheses - .get(&(std::ptr::from_ref(term) as usize)) - .map(|index| hypothesis(*index)); - self.select( - ctx, - scrutinee, - &self.src(scrutinee, ctx)?, - branches, - &alternatives, - &result, - hypothesis.as_deref(), - )? - } - SemanticTerm::Add { left, right } => binary("+", left, right)?, - SemanticTerm::Beq { left, right } => format!( - "(Nat.beq {} {})", - self.src(left, ctx)?, - self.src(right, ctx)? - ), - SemanticTerm::Ble { left, right } => format!( - "(Nat.ble {} {})", - self.src(left, ctx)?, - self.src(right, ctx)? - ), - SemanticTerm::Blt { left, right } => format!( - "(Nat.blt {} {})", - self.src(left, ctx)?, - self.src(right, ctx)? - ), - SemanticTerm::And { left, right } => binary("&&", left, right)?, - SemanticTerm::Or { left, right } => binary("||", left, right)?, - SemanticTerm::Not { value } => format!("(!{})", self.src(value, ctx)?), - SemanticTerm::Let { - binder, - value, - body, - } => { - let (local, generic) = self.let_local(binder, ctx); - let ty = local.ty.clone(); - let inner = ctx.with(vec![(local, generic)]); - format!( - "(let {} : {} := {}; {})", - identifier(&binder.name), - self.ty(&ty)?, - self.src(value, ctx)?, - self.src(body, &inner)? - ) - } - SemanticTerm::Pair { left, right } => { - format!("({}, {})", self.src(left, ctx)?, self.src(right, ctx)?) - } - SemanticTerm::First { value } => format!("({}).1", self.src(value, ctx)?), - SemanticTerm::Second { value } => format!("({}).2", self.src(value, ctx)?), - SemanticTerm::Lambda { - parameters, - captures: _, - body, - } => { - let bound: Vec<(Local, SemanticType)> = parameters - .iter() - .map(|binder| self.let_local(binder, ctx)) - .collect(); - let mut out = String::from("(fun"); - for (local, _) in &bound { - out.push_str(&format!( - " ({} : {})", - identifier(&local.name), - self.ty(&local.ty)? - )); - } - out.push_str(&format!(" => {})", self.src(body, &ctx.with(bound))?)); - out - } - SemanticTerm::Apply { - function, - arguments, - } => format!( - "({}{})", - self.src(function, ctx)?, - self.src_terms(arguments, ctx)? - ), - SemanticTerm::FunctionRef { - function, - type_arguments, - } => { - let mut out = format!("({}", self.member(function, site)?); - for argument in type_arguments { - out.push(' '); - out.push_str(&self.ty(&self.source.close(argument, site))?); - } - out.push(')'); - out - } - SemanticTerm::MapLiteral { - key, - value, - entries, - } => format!( - "({} : List (Prod {} {}))", - list( - entries - .iter() - .map(|SemanticMapEntry { key, value }| { - Ok::(format!( - "({}, {})", - self.src(key, ctx)?, - self.src(value, ctx)? - )) - }) - .collect::, _>>()? - ), - self.ty(&self.source.close(key, site))?, - self.ty(&self.source.close(value, site))? - ), - SemanticTerm::SetLiteral { element, elements } => format!( - "({} : List {})", - list( - elements - .iter() - .map(|element| self.src(element, ctx)) - .collect::, _>>()? - ), - self.ty(&self.source.close(element, site))? - ), - // Certificates read elaborated declarations, in which a checked - // application is the ordinary term it means (§17.12). - SemanticTerm::CheckedApply { - model: _, - type_arguments: _, - arguments: _, - checks: _, - } => { - return Err( - "a checked model application is certified only through its elaboration" - .to_owned(), - ); - } - SemanticTerm::GraphLiteral { node, nodes, edges } => { - let node = self.ty(&self.source.close(node, site))?; - let mut items = Vec::new(); - for source in nodes { - let mut targets = Vec::new(); - for SemanticEdge { - source: from, - target, - } in edges - { - if from == source { - targets.push(self.src(target, ctx)?); - } - } - items.push(format!("({}, {})", self.src(source, ctx)?, list(targets))); - } - format!("({} : List (Prod {node} (List {node})))", list(items)) - } - SemanticTerm::Eq { left: _, right: _ } - | SemanticTerm::Le { left: _, right: _ } - | SemanticTerm::Lt { left: _, right: _ } - | SemanticTerm::PropAnd { left: _, right: _ } - | SemanticTerm::Implies { - premise: _, - conclusion: _, - } - | SemanticTerm::Iff { left: _, right: _ } - | SemanticTerm::Forall { binder: _, body: _ } => { - return Err(format!( - "the formal-only construct `{}` has no runtime denotation", - super::eligibility::term_key(term) - )); - } - }) - } -} +impl Gen<'_> {} /// The runtime namespace and name a primitive is rendered with. const fn primitive_name(operation: SemanticPrimitive) -> (&'static str, &'static str) { @@ -1870,46 +1172,6 @@ const fn primitive_name(operation: SemanticPrimitive) -> (&'static str, &'static } } -/// The source definitions a term calls, as `{module, name}` references. -/// Read from the term's serialized form, which names every call the same -/// way, so no construct is missed or met by default. -fn called_members(term: &SemanticTerm) -> Vec { - fn walk(value: &serde_json::Value, out: &mut Vec) { - match value { - serde_json::Value::Object(map) => { - let call = map.get("kind").and_then(serde_json::Value::as_str) == Some("call"); - match (call, map.get("function")) { - (true, Some(function)) => { - match serde_json::from_value::(function.clone()) { - Ok(member) => out.push(member), - Err(_) => {} - } - } - (true, None) | (false, Some(_) | None) => {} - } - for inner in map.values() { - walk(inner, out); - } - } - serde_json::Value::Array(items) => { - for inner in items { - walk(inner, out); - } - } - serde_json::Value::Null - | serde_json::Value::Bool(_) - | serde_json::Value::Number(_) - | serde_json::Value::String(_) => {} - } - } - let mut out = Vec::new(); - match serde_json::to_value(term) { - Ok(value) => walk(&value, &mut out), - Err(_) => {} - } - out -} - /// The fixed-width suffix of a library lemma. fn width_suffix(ty: &SemanticType) -> Result<&'static str, String> { fixed_kind(ty) @@ -2177,232 +1439,8 @@ fn equal_suffix(ty: &SemanticType) -> Result<&'static str, String> { } } -impl Gen<'_> { - /// The relation lemma of a directly realized primitive at its operand - /// types. - #[allow(clippy::too_many_lines)] - fn prim_rule( - &self, - operation: SemanticPrimitive, - types: &[SemanticType], - result: &SemanticType, - site: &Site, - ) -> Result { - let first = types - .first() - .ok_or_else(|| format!("{operation:?} has no operand"))?; - let rule = |lemma: String, width: Width| PrimRule { - lemma, - prefix: Vec::new(), - width, - }; - let numeric_rule = |nat: &str, nat_width: Width, int: &str| -> Result { - Ok(match numeric(first)? { - Numeric::Nat => rule(lib(nat), nat_width), - Numeric::Int => rule(lib(int), Width::Int), - }) - }; - Ok(match operation { - SemanticPrimitive::Subtract => { - numeric_rule("prim_natSub", Width::Exact, "prim_intSub")? - } - SemanticPrimitive::Multiply => numeric_rule("prim_natMul", Width::Nat, "prim_intMul")?, - SemanticPrimitive::Quotient => { - numeric_rule("prim_natQuot", Width::Exact, "prim_intQuot")? - } - SemanticPrimitive::Remainder => { - numeric_rule("prim_natRem", Width::Exact, "prim_intRem")? - } - SemanticPrimitive::Negate => rule(lib("prim_intNeg"), Width::Int), - SemanticPrimitive::CheckedConvert => rule( - lib(&format!( - "prim_convert_{}_{}", - integer_suffix(first)?, - width_suffix(&option_value(result)?)? - )), - Width::Exact, - ), - SemanticPrimitive::CheckedAdd => rule( - lib(&format!("prim_checkedAdd_{}", width_suffix(first)?)), - Width::Exact, - ), - SemanticPrimitive::CheckedSubtract => rule( - lib(&format!("prim_checkedSub_{}", width_suffix(first)?)), - Width::Exact, - ), - // The 64-bit signed loops are taken by their clauses (§17.17.3). - SemanticPrimitive::CheckedMultiply => { - let suffix = width_suffix(first)?; - PrimRule { - lemma: lib(&format!("prim_checkedMul_{suffix}")), - prefix: if suffix == "i64" { - vec![ - self.runtime(site, "LexLeanRuntime", "multiplyMagnitudeInt64")?, - "(fun _ _ _ _ => rfl)".to_owned(), - "(fun _ _ _ _ _ => rfl)".to_owned(), - ] - } else { - Vec::new() - }, - width: Width::Exact, - } - } - SemanticPrimitive::CheckedNegate => rule( - lib(&format!("prim_checkedNeg_{}", width_suffix(first)?)), - Width::Exact, - ), - SemanticPrimitive::CheckedQuotient => { - let suffix = width_suffix(first)?; - PrimRule { - lemma: lib(&format!("prim_checkedQuot_{suffix}")), - prefix: if suffix == "i64" { - vec![ - self.runtime(site, "LexLeanRuntime", "divideMagnitudeInt64")?, - "(fun _ _ _ _ => rfl)".to_owned(), - "(fun _ _ _ _ _ => rfl)".to_owned(), - ] - } else { - Vec::new() - }, - width: Width::Exact, - } - } - SemanticPrimitive::BitAnd => rule( - lib(&format!("prim_bitAnd_{}", width_suffix(first)?)), - Width::Exact, - ), - SemanticPrimitive::BitOr => rule( - lib(&format!("prim_bitOr_{}", width_suffix(first)?)), - Width::Exact, - ), - SemanticPrimitive::BitXor => rule( - lib(&format!("prim_bitXor_{}", width_suffix(first)?)), - Width::Exact, - ), - SemanticPrimitive::BitNot => rule( - lib(&format!("prim_bitNot_{}", width_suffix(first)?)), - Width::Exact, - ), - SemanticPrimitive::ShiftLeft => rule( - lib(&format!("prim_shiftLeft_{}", width_suffix(first)?)), - Width::Exact, - ), - SemanticPrimitive::ShiftRight => rule( - lib(&format!("prim_shiftRight_{}", width_suffix(first)?)), - Width::Exact, - ), - SemanticPrimitive::Append => match sequence(first)? { - Sequence::List(_) => PrimRule { - lemma: lib("prim_append_list"), - prefix: vec![self.list_bundle(first)?], - width: Width::Exact, - }, - Sequence::Bytes => rule(lib("prim_append_bytes"), Width::Exact), - Sequence::String => return Err("Append of strings".to_owned()), - }, - SemanticPrimitive::Length => match sequence(first)? { - Sequence::List(_) => PrimRule { - lemma: lib("prim_length_list"), - prefix: vec![self.list_bundle(first)?], - width: Width::Nat, - }, - Sequence::Bytes => rule(lib("prim_length_bytes"), Width::Nat), - Sequence::String => rule(lib("prim_length_string"), Width::Nat), - }, - SemanticPrimitive::MapSize | SemanticPrimitive::SetSize => PrimRule { - lemma: lib("prim_length_list"), - prefix: vec![self.list_bundle(first)?], - width: Width::Nat, - }, - SemanticPrimitive::Index => match sequence(first)? { - Sequence::List(element) => PrimRule { - lemma: lib("prim_index_list"), - prefix: vec![ - self.list_bundle(first)?, - self.option_bundle(&SemanticType::Option { - value: Box::new(element), - })?, - "(fun _ => rfl)".to_owned(), - self.runtime(site, "LexLeanRuntime", "listIndex")?, - "(fun _ => rfl)".to_owned(), - "(fun _ _ => rfl)".to_owned(), - "(fun _ _ _ => rfl)".to_owned(), - ], - width: Width::Exact, - }, - Sequence::Bytes => rule(lib("prim_index_bytes"), Width::Exact), - Sequence::String => return Err("Index of a string".to_owned()), - }, - SemanticPrimitive::Slice => match sequence(first)? { - Sequence::List(_) => PrimRule { - lemma: lib("prim_slice_list"), - prefix: vec![ - self.list_bundle(first)?, - self.option_bundle(&SemanticType::Option { - value: Box::new(first.clone()), - })?, - "(fun _ => rfl)".to_owned(), - ], - width: Width::Exact, - }, - Sequence::Bytes => rule(lib("prim_slice_bytes"), Width::Exact), - Sequence::String => return Err("Slice of a string".to_owned()), - }, - SemanticPrimitive::Utf8Encode => rule(lib("prim_utf8Encode"), Width::Exact), - SemanticPrimitive::Utf8Decode => rule(lib("prim_utf8Decode"), Width::Exact), - SemanticPrimitive::CompareBytes => rule(lib("prim_compareBytes"), Width::Exact), - SemanticPrimitive::Equal => rule( - lib(&format!("prim_equal_{}", equal_suffix(first)?)), - Width::Exact, - ), - SemanticPrimitive::SplitExact => rule(lib("prim_splitExact"), Width::Exact), - SemanticPrimitive::Join => rule(lib("prim_join"), Width::Exact), - SemanticPrimitive::ParseDecimal => { - let target = option_value(result)?; - match numeric(&target) { - Ok(Numeric::Int) => rule( - lib("prim_parseDecimal_int"), - Width::Predicate("decimalFits"), - ), - Ok(Numeric::Nat) | Err(_) => rule( - lib(&format!("prim_parseDecimal_{}", width_suffix(&target)?)), - Width::Exact, - ), - } - } - SemanticPrimitive::FormatDecimal => rule( - lib(&format!("prim_formatDecimal_{}", integer_suffix(first)?)), - Width::Exact, - ), - SemanticPrimitive::MapEntries - | SemanticPrimitive::SetElements - | SemanticPrimitive::MapInsert - | SemanticPrimitive::MapRemove - | SemanticPrimitive::MapLookup - | SemanticPrimitive::MapContains - | SemanticPrimitive::MapKeys - | SemanticPrimitive::MapValues - | SemanticPrimitive::MapFold - | SemanticPrimitive::SetInsert - | SemanticPrimitive::SetRemove - | SemanticPrimitive::SetContains - | SemanticPrimitive::SetUnion - | SemanticPrimitive::SetIntersection - | SemanticPrimitive::SetDifference - | SemanticPrimitive::SetFold - | SemanticPrimitive::ListFold - | SemanticPrimitive::Iterate - | SemanticPrimitive::IterateUntil - | SemanticPrimitive::GraphSuccessors - | SemanticPrimitive::GraphReachable - | SemanticPrimitive::GraphTopological - | SemanticPrimitive::LessThan => { - return Err(format!("{operation:?} is not a direct primitive")); - } - }) - } -} - +impl Gen<'_> {} + impl Gen<'_> { /// The function index of the next lambda of `function`, in the order /// lowering met it. @@ -2439,310 +1477,9 @@ impl Gen<'_> { .copied() .ok_or_else(|| format!("`{key}` is not in the layout")) } - - /// The arguments a callee's relation and fits take for one source - /// argument: the value, and for a function value its companion. - /// Returns `(fits arguments, relation arguments, operand proof)`. - fn argument( - &mut self, - argument: &SemanticTerm, - ctx: &Ctx, - ) -> Result<(String, String, Proof), String> { - match self.function_argument(argument, ctx)? { - Some(companion) => Ok(( - format!("({}) ({})", companion.value, companion.fits), - format!( - "({}) ({}) ({}) ({}) ({})", - companion.value, - companion.fits, - companion.index, - companion.captures, - companion.relation - ), - companion.proof, - )), - None => { - let proof = self.prove(argument, ctx)?; - let value = format!("({})", self.src(argument, ctx)?); - Ok((value.clone(), value, proof)) - } - } - } - - /// A function-typed argument's value and companions: its fits function, - /// closure index, encoded captures, and relation; `None` for a - /// first-order argument. - fn function_argument( - &mut self, - argument: &SemanticTerm, - ctx: &Ctx, - ) -> Result, String> { - let ty = self.source.infer(argument, &ctx.sources(), &ctx.site)?; - let parameters = match function_type(&ty) { - Some((parameters, _)) => parameters.to_vec(), - None => return Ok(None), - }; - let binders: Vec = (0..parameters.len()) - .map(|index| format!("__p{index}")) - .collect(); - let typed: String = binders - .iter() - .zip(¶meters) - .map(|(binder, ty)| Ok::(format!(" ({binder} : {})", self.ty(ty)?))) - .collect::, _>>()? - .concat(); - let applied = binders.join(" "); - match argument { - SemanticTerm::Var { name } => { - let local = ctx.lookup(name)?; - let companion = local - .companion - .as_ref() - .ok_or_else(|| format!("function-typed `{name}` has no companion"))?; - Ok(Some(FunctionArgument { - value: identifier(name), - fits: companion.fits.clone(), - index: companion.index.clone(), - captures: companion.captures.clone(), - relation: companion.relation.clone(), - proof: Proof { - proof: format!("({} rfl)", lib("conv_var")), - fits: TRUE.to_owned(), - }, - })) - } - SemanticTerm::Lambda { - parameters: _, - captures, - body: _, - } => { - let index = self.next_lambda(ctx.function)?; - let mut fits_captures = String::new(); - let mut relation_captures = String::new(); - let mut encoded = Vec::new(); - let mut proofs = Vec::new(); - for capture in captures { - let local = ctx.lookup(capture)?.clone(); - let name = identifier(capture); - match &local.companion { - Some(companion) => { - fits_captures.push_str(&format!(" ({name}) ({})", companion.fits)); - relation_captures.push_str(&format!( - " ({name}) ({}) ({}) ({}) ({})", - companion.fits, - companion.index, - companion.captures, - companion.relation - )); - } - None => { - fits_captures.push_str(&format!(" ({name})")); - relation_captures.push_str(&format!(" ({name})")); - } - } - encoded.push(self.encoded_local(&local)?); - proofs.push(Proof { - proof: format!("({} rfl)", lib("conv_var")), - fits: TRUE.to_owned(), - }); - } - let captured = operands(proofs); - Ok(Some(FunctionArgument { - value: self.src(argument, ctx)?, - fits: format!("fun{typed} => __fits_{index}{fits_captures} {applied}"), - index: index.to_string(), - captures: list(encoded), - relation: format!("fun{typed} => __rel_{index}{relation_captures} {applied}"), - proof: Proof { - proof: format!("({} {})", lib("conv_closure"), captured.proof), - fits: captured.fits, - }, - })) - } - SemanticTerm::FunctionRef { - function, - type_arguments, - } => { - let module = Source::module_of(function, &ctx.site); - let closed: Vec = type_arguments - .iter() - .map(|argument| self.source.close(argument, &ctx.site)) - .collect(); - let index = self.instance_index(&module, &function.name, &closed)?; - Ok(Some(FunctionArgument { - value: self.src(argument, ctx)?, - fits: format!("fun{typed} => __fits_{index} {applied}"), - index: index.to_string(), - captures: "[]".to_owned(), - relation: format!("fun{typed} => __rel_{index} {applied}"), - proof: Proof { - proof: format!("({} {})", lib("conv_closure"), lib("convL_nil")), - fits: TRUE.to_owned(), - }, - })) - } - SemanticTerm::Nat { value: _ } - | SemanticTerm::Integer { - representation: _, - value: _, - } - | SemanticTerm::String { value: _ } - | SemanticTerm::Bytes { hex: _ } - | SemanticTerm::Primitive { - operation: _, - arguments: _, - result: _, - } - | SemanticTerm::Bool { value: _ } - | SemanticTerm::Unit - | SemanticTerm::Nil { element: _ } - | SemanticTerm::Cons { head: _, tail: _ } - | SemanticTerm::Record { - r#type: _, - type_arguments: _, - fields: _, - } - | SemanticTerm::Constructor { - constructor: _, - type_arguments: _, - arguments: _, - } - | SemanticTerm::InstanceValue { - class: _, - arguments: _, - resolved: _, - } - | SemanticTerm::Project { value: _, field: _ } - | SemanticTerm::Call { - function: _, - type_arguments: _, - arguments: _, - } - | SemanticTerm::If { - condition: _, - then_value: _, - else_value: _, - } - | SemanticTerm::Match { - scrutinee: _, - branches: _, - } - | SemanticTerm::Eq { left: _, right: _ } - | SemanticTerm::Le { left: _, right: _ } - | SemanticTerm::Lt { left: _, right: _ } - | SemanticTerm::Add { left: _, right: _ } - | SemanticTerm::Beq { left: _, right: _ } - | SemanticTerm::Ble { left: _, right: _ } - | SemanticTerm::Blt { left: _, right: _ } - | SemanticTerm::And { left: _, right: _ } - | SemanticTerm::PropAnd { left: _, right: _ } - | SemanticTerm::Or { left: _, right: _ } - | SemanticTerm::Not { value: _ } - | SemanticTerm::Implies { - premise: _, - conclusion: _, - } - | SemanticTerm::Iff { left: _, right: _ } - | SemanticTerm::Forall { binder: _, body: _ } - | SemanticTerm::Let { - binder: _, - value: _, - body: _, - } - | SemanticTerm::Pair { left: _, right: _ } - | SemanticTerm::First { value: _ } - | SemanticTerm::Second { value: _ } - | SemanticTerm::Apply { - function: _, - arguments: _, - } - | SemanticTerm::MapLiteral { - key: _, - value: _, - entries: _, - } - | SemanticTerm::SetLiteral { - element: _, - elements: _, - } - | SemanticTerm::GraphLiteral { - node: _, - nodes: _, - edges: _, - } - | SemanticTerm::CheckedApply { - model: _, - type_arguments: _, - arguments: _, - checks: _, - } => Err(format!( - "a function value `{}` is neither a parameter, a lambda, nor a function reference", - super::eligibility::term_key(argument) - )), - } - } } impl Gen<'_> { - fn conv_var() -> Proof { - Proof { - proof: format!("({} rfl)", lib("conv_var")), - fits: TRUE.to_owned(), - } - } - - fn proofs(&mut self, terms: &[SemanticTerm], ctx: &Ctx) -> Result, String> { - terms.iter().map(|term| self.prove(term, ctx)).collect() - } - - /// The constructor lemma and fits of building `constructor`. - fn construct_lemma(&self, constructor: &Constructor, whole: &str) -> (String, String) { - match constructor { - Constructor::Bool(true) => (lib("construct_true"), TRUE.to_owned()), - Constructor::Bool(false) => (lib("construct_false"), TRUE.to_owned()), - Constructor::Zero => (lib("construct_zero"), TRUE.to_owned()), - Constructor::Succ => ( - lib("construct_succ"), - format!("(Nat.blt {whole} {NAT_BOUND})"), - ), - Constructor::Nil => (lib("construct_nil"), TRUE.to_owned()), - Constructor::Cons => (lib("construct_cons"), TRUE.to_owned()), - Constructor::OptionNone => (lib("construct_none"), TRUE.to_owned()), - Constructor::OptionSome => (lib("construct_some"), TRUE.to_owned()), - Constructor::Ok => (lib("construct_ok"), TRUE.to_owned()), - Constructor::Error => (lib("construct_error"), TRUE.to_owned()), - Constructor::Violation(_, _) => (lib("construct_pair"), TRUE.to_owned()), - Constructor::Document { ty: _, index: _ } => (lib("construct_adt"), TRUE.to_owned()), - } - } - - /// A literal list built as a cons chain. - fn cons_chain(items: Vec) -> Proof { - items.into_iter().rev().fold( - built(operands(Vec::new()), &lib("construct_nil"), TRUE), - |tail, head| built(operands(vec![head, tail]), &lib("construct_cons"), TRUE), - ) - } - - /// The fields of a record of closed type `ty`, in declaration order. - fn record( - &mut self, - ty: &SemanticType, - fields: &[SemanticAssignment], - ctx: &Ctx, - ) -> Result { - let shape = self.source.document(ty)?; - let mut proofs = Vec::new(); - for field in &shape.field_names { - let SemanticAssignment { field: _, value } = fields - .iter() - .find(|assignment| &assignment.field == field) - .ok_or_else(|| format!("field `{field}` is not assigned"))?; - proofs.push(self.prove(value, ctx)?); - } - Ok(built(operands(proofs), &lib("construct_adt"), TRUE)) - } - /// Refuse a part of a certificate of `len` bytes when it is larger than /// the certificate may be. fn within(&self, len: usize) -> Result<(), String> { @@ -2755,4480 +1492,1292 @@ impl Gen<'_> { } Ok(()) } +} - /// The proof of a term, refused as soon as it is larger than the - /// certificate may be: a proof contains those of its subterms, so no - /// part of a certificate that passes this is beyond the limit. - fn prove(&mut self, term: &SemanticTerm, ctx: &Ctx) -> Result { - let proof = self.prove_term(term, ctx)?; - self.within(proof.proof.len())?; - Ok(proof) +impl Gen<'_> { + /// A scope binding `locals` in order, function-typed ones with companions + /// named by `stem` and position. + fn scope(locals: &[(Local, SemanticType)], stem: &str) -> Vec { + locals + .iter() + .enumerate() + .map(|(position, (local, generic))| CLocal { + local: local.clone(), + generic: generic.clone(), + companion: function_type(&local.ty) + .map(|_| companion_names(&format!("{stem}{position}"))), + }) + .collect() } +} - #[allow(clippy::too_many_lines)] - fn prove_term(&mut self, term: &SemanticTerm, ctx: &Ctx) -> Result { - let site = ctx.site.clone(); - let value = || Proof { - proof: lib("conv_value"), - fits: TRUE.to_owned(), - }; - Ok(match term { - SemanticTerm::Var { name: _ } => Self::conv_var(), - SemanticTerm::Nat { value: _ } - | SemanticTerm::Integer { - representation: _, - value: _, - } - | SemanticTerm::String { value: _ } - | SemanticTerm::Bytes { hex: _ } => value(), - SemanticTerm::Primitive { - operation, - arguments, - result, - } => self.primitive(term, *operation, arguments, result, ctx)?, - SemanticTerm::Bool { value } => built( - operands(Vec::new()), - &lib(if *value { - "construct_true" - } else { - "construct_false" - }), - TRUE, - ), - SemanticTerm::Unit => built(operands(Vec::new()), &lib("construct_unit"), TRUE), - SemanticTerm::Nil { element: _ } => { - built(operands(Vec::new()), &lib("construct_nil"), TRUE) - } - SemanticTerm::Cons { head, tail } => { - let head = self.prove(head, ctx)?; - let tail = self.prove(tail, ctx)?; - built(operands(vec![head, tail]), &lib("construct_cons"), TRUE) - } - SemanticTerm::Record { - r#type: _, - type_arguments: _, - fields, - } => { - let ty = self.source.infer(term, &ctx.sources(), &site)?; - self.record(&ty, fields, ctx)? +/// Strongly connected components of `graph` in dependency-first order. +fn components(graph: &BTreeMap>) -> Vec> { + struct State<'g> { + graph: &'g BTreeMap>, + index: BTreeMap, + low: BTreeMap, + stack: Vec, + on_stack: BTreeSet, + next: usize, + out: Vec>, + } + fn visit(state: &mut State<'_>, node: u64) { + state.index.insert(node, state.next); + state.low.insert(node, state.next); + state.next += 1; + state.stack.push(node); + state.on_stack.insert(node); + let successors: Vec = state + .graph + .get(&node) + .map(|edges| edges.iter().copied().collect()) + .unwrap_or_default(); + for successor in successors { + if !state.graph.contains_key(&successor) { + continue; } - SemanticTerm::Constructor { - constructor, - type_arguments, - arguments, - } => { - let closed: Vec = type_arguments - .iter() - .map(|argument| self.source.close(argument, &site)) - .collect(); - let (constructor, _) = self.source.constructor(constructor, &closed, &site)?; - let proofs = match constructor { - // A violation builds the pair of its two Booleans. - Constructor::Violation(first, second) => { - let boolean = |value: bool| { - built( - operands(Vec::new()), - &lib(if value { - "construct_true" - } else { - "construct_false" - }), - TRUE, - ) - }; - vec![boolean(first), boolean(second)] + match state.index.get(&successor).copied() { + None => { + visit(state, successor); + let low = state.low[&node].min(state.low[&successor]); + state.low.insert(node, low); + } + Some(index) => { + if state.on_stack.contains(&successor) { + let low = state.low[&node].min(index); + state.low.insert(node, low); } - Constructor::Bool(_) - | Constructor::Zero - | Constructor::Succ - | Constructor::Nil - | Constructor::Cons - | Constructor::OptionNone - | Constructor::OptionSome - | Constructor::Ok - | Constructor::Error - | Constructor::Document { ty: _, index: _ } => self.proofs(arguments, ctx)?, - }; - let whole = self.src(term, ctx)?; - let (lemma, fits) = self.construct_lemma(&constructor, &whole); - built(operands(proofs), &lemma, &fits) - } - SemanticTerm::InstanceValue { - class: _, - arguments: _, - resolved, - } => { - let module = Source::module_of(resolved, &site); - let index = self.instance_index(&module, &resolved.name, &[])?; - Proof { - proof: format!("({} {} __rel_{index})", lib("conv_call"), lib("convL_nil")), - fits: and(TRUE, &format!("__fits_{index}")), } } - SemanticTerm::Project { value, field: _ } => { - let inner = self.prove(value, ctx)?; - Proof { - proof: format!("({} {} rfl)", lib("conv_field"), inner.proof), - fits: inner.fits, + } + if state.low[&node] == state.index[&node] { + let mut component = Vec::new(); + loop { + let member = state.stack.pop().expect("the node is on the stack"); + state.on_stack.remove(&member); + component.push(member); + if member == node { + break; } } - SemanticTerm::Call { - function, - type_arguments, - arguments, - } => { - let module = Source::module_of(function, &site); - let closed: Vec = type_arguments - .iter() - .map(|argument| self.source.close(argument, &site)) - .collect(); - let index = self.instance_index(&module, &function.name, &closed)?; - let mut fits_arguments = String::new(); - let mut relation_arguments = String::new(); - let mut proofs = Vec::new(); - for argument in arguments { - let (fits, relation, proof) = self.argument(argument, ctx)?; - fits_arguments.push(' '); - fits_arguments.push_str(&fits); - relation_arguments.push(' '); - relation_arguments.push_str(&relation); - proofs.push(proof); - } - let list = operands(proofs); - Proof { - proof: format!( - "({} {} (__rel_{index}{relation_arguments}))", - lib("conv_call"), - list.proof - ), - fits: and(&list.fits, &format!("(__fits_{index}{fits_arguments})")), + component.sort_unstable(); + state.out.push(component); + } + } + let mut state = State { + graph, + index: BTreeMap::new(), + low: BTreeMap::new(), + stack: Vec::new(), + on_stack: BTreeSet::new(), + next: 0, + out: Vec::new(), + }; + for node in graph.keys() { + if !state.index.contains_key(node) { + visit(&mut state, *node); + } + } + state.out +} + +impl<'a> Gen<'a> { + /// The hypothesis numbering of a well-founded definition body. + fn hypotheses( + name: &str, + group: &BTreeSet, + type_parameters: &[String], + parameters: &[SemanticParameter], + body: &SemanticTerm, + ) -> Result { + let plan = + crate::ir::semantic::well_founded_plan(name, group, type_parameters, parameters, body)?; + Ok(plan + .addresses + .iter() + .enumerate() + .map(|(index, address)| (*address, index)) + .collect()) + } + + /// The well-founded group of a definition: itself, or every member of + /// its mutual label. + fn group(&self, module: &str, name: &str, mutual: Option<&String>) -> BTreeSet { + let mut group = BTreeSet::from([name.to_owned()]); + let label = match mutual { + Some(label) => label, + None => return group, + }; + for declaration in self + .source + .modules + .get(module) + .map(|linked| linked.semantic.lowered_declarations()) + .unwrap_or_default() + { + match declaration { + SemanticDeclaration::Definition { + name, + type_parameters: _, + parameters: _, + result: _, + recursive_argument: _, + body: _, + axioms: _, + executable: _, + mutual: Some(other), + termination: _, + production: _, + } => { + if other == label { + group.insert(name.clone()); + } } - } - SemanticTerm::If { - condition, - then_value, - else_value, - } => self.conditional(term, condition, then_value, else_value, ctx)?, - SemanticTerm::Match { - scrutinee, - branches, - } => self.matching(term, scrutinee, branches, ctx)?, - SemanticTerm::Add { left, right } => { - let list = operands(vec![self.prove(left, ctx)?, self.prove(right, ctx)?]); - let (left, right) = (self.src(left, ctx)?, self.src(right, ctx)?); - Proof { - proof: format!( - "({} {} ({} {left} {right}))", - lib("conv_prim"), - list.proof, - lib("prim_natAdd") - ), - fits: and( - &list.fits, - &format!("(Nat.blt ({left} + {right}) {NAT_BOUND})"), - ), + SemanticDeclaration::Definition { + name: _, + type_parameters: _, + parameters: _, + result: _, + recursive_argument: _, + body: _, + axioms: _, + executable: _, + mutual: None, + termination: _, + production: _, } - } - SemanticTerm::Beq { left, right } => self.comparison("prim_natEq", left, right, ctx)?, - SemanticTerm::Ble { left, right } => self.comparison("prim_natLe", left, right, ctx)?, - SemanticTerm::Blt { left, right } => self.comparison("prim_natLt", left, right, ctx)?, - // `a && b` is `if a then b else false` and `a || b` is - // `if a then true else b` (§17.14). - SemanticTerm::And { left, right } => { - let condition = self.prove(left, ctx)?; - let then_branch = self.prove(right, ctx)?; - let else_branch = built(operands(Vec::new()), &lib("construct_false"), TRUE); - let (left, right) = (self.src(left, ctx)?, self.src(right, ctx)?); - Proof { - proof: format!( - "({} (fun (__b : Bool) => if __b then {} else {}) (fun (__b : Bool) => {} (__b && {right})) {left} {} (fun _ => {}) (fun _ => {}))", - lib("conv_cond"), - then_branch.fits, - else_branch.fits, - value_ctor("bool"), - condition.proof, - then_branch.proof, - else_branch.proof - ), - fits: and( - &condition.fits, - &format!( - "(if {left} then {} else {})", - then_branch.fits, else_branch.fits - ), - ), + | SemanticDeclaration::Structure { + name: _, + type_parameters: _, + parameters: _, + fields: _, } - } - SemanticTerm::Or { left, right } => { - let condition = self.prove(left, ctx)?; - let then_branch = built(operands(Vec::new()), &lib("construct_true"), TRUE); - let else_branch = self.prove(right, ctx)?; - let (left, right) = (self.src(left, ctx)?, self.src(right, ctx)?); - Proof { - proof: format!( - "({} (fun (__b : Bool) => if __b then {} else {}) (fun (__b : Bool) => {} (__b || {right})) {left} {} (fun _ => {}) (fun _ => {}))", - lib("conv_cond"), - then_branch.fits, - else_branch.fits, - value_ctor("bool"), - condition.proof, - then_branch.proof, - else_branch.proof - ), - fits: and( - &condition.fits, - &format!( - "(if {left} then {} else {})", - then_branch.fits, else_branch.fits - ), - ), + | SemanticDeclaration::Class { + name: _, + type_parameters: _, + parameters: _, + fields: _, } - } - SemanticTerm::Not { value } => { - let list = operands(vec![self.prove(value, ctx)?]); - Proof { - proof: format!( - "({} {} ({} {}))", - lib("conv_prim"), - list.proof, - lib("prim_boolNot"), - self.src(value, ctx)? - ), - fits: and(&list.fits, TRUE), + | SemanticDeclaration::Instance { + name: _, + class: _, + arguments: _, + priority: _, + fields: _, } - } - SemanticTerm::Let { - binder, - value, - body, - } => { - let (local, generic) = self.let_local(binder, ctx); - let ty = local.ty.clone(); - let bound = self.prove(value, ctx)?; - let inner = ctx.with(vec![(local, generic)]); - let rest = self.prove(body, &inner)?; - let binding = format!( - "let {} : {} := {};", - identifier(&binder.name), - self.ty(&ty)?, - self.src(value, ctx)? - ); - Proof { - proof: format!( - "({binding} {} {} {})", - lib("conv_let"), - bound.proof, - rest.proof - ), - fits: and(&bound.fits, &format!("({binding} {})", rest.fits)), + | SemanticDeclaration::Inductive { + name: _, + type_parameters: _, + parameters: _, + constructors: _, + mutual: _, } - } - SemanticTerm::Pair { left, right } => { - let list = operands(vec![self.prove(left, ctx)?, self.prove(right, ctx)?]); - built(list, &lib("construct_pair"), TRUE) - } - SemanticTerm::First { value } => { - let inner = self.prove(value, ctx)?; - Proof { - proof: format!("({} {})", lib("conv_first"), inner.proof), - fits: inner.fits, + | SemanticDeclaration::Theorem { + name: _, + type_parameters: _, + parameters: _, + statement: _, + proof: _, + axioms: _, } - } - SemanticTerm::Second { value } => { - let inner = self.prove(value, ctx)?; - Proof { - proof: format!("({} {})", lib("conv_second"), inner.proof), - fits: inner.fits, + | SemanticDeclaration::Artifact { + name: _, + role: _, + sha256: _, + length: _, + schema: _, + r#type: _, + axioms: _, } - } - SemanticTerm::Apply { - function, - arguments, - } => self.application(function, arguments, ctx)?, - SemanticTerm::MapLiteral { - key: _, - value: _, - entries, - } => { - let mut items = Vec::new(); - for SemanticMapEntry { key, value } in entries { - let list = operands(vec![self.prove(key, ctx)?, self.prove(value, ctx)?]); - items.push(built(list, &lib("construct_pair"), TRUE)); + | SemanticDeclaration::Contract { + name: _, + type_parameters: _, + input: _, + output: _, + state: _, + precondition: _, + postcondition: _, + invariant: _, + validators: _, + axioms: _, } - Self::cons_chain(items) - } - SemanticTerm::SetLiteral { - element: _, - elements, - } => { - let items = self.proofs(elements, ctx)?; - Self::cons_chain(items) - } - // Certificates read elaborated declarations, in which a checked - // application is the ordinary term it means (§17.12). - SemanticTerm::CheckedApply { - model: _, - type_arguments: _, - arguments: _, - checks: _, - } => { - return Err( - "a checked model application is certified only through its elaboration" - .to_owned(), - ); - } - SemanticTerm::GraphLiteral { - node: _, - nodes, - edges, - } => { - let mut items = Vec::new(); - for source in nodes { - let mut targets = Vec::new(); - for SemanticEdge { - source: from, - target, - } in edges - { - if from == source { - targets.push(self.prove(target, ctx)?); - } - } - let source = self.prove(source, ctx)?; - let list = operands(vec![source, Self::cons_chain(targets)]); - items.push(built(list, &lib("construct_pair"), TRUE)); + | SemanticDeclaration::Realization { + name: _, + type_parameters: _, + input: _, + output: _, + state: _, + descriptor: _, + executable: _, + axioms: _, } - Self::cons_chain(items) - } - SemanticTerm::Lambda { - parameters: _, - captures: _, - body: _, - } - | SemanticTerm::FunctionRef { - function: _, - type_arguments: _, - } => { - return Err( - "a function value outside an argument or application position".to_owned(), - ); - } - SemanticTerm::Eq { left: _, right: _ } - | SemanticTerm::Le { left: _, right: _ } - | SemanticTerm::Lt { left: _, right: _ } - | SemanticTerm::PropAnd { left: _, right: _ } - | SemanticTerm::Implies { - premise: _, - conclusion: _, - } - | SemanticTerm::Iff { left: _, right: _ } - | SemanticTerm::Forall { binder: _, body: _ } => { - return Err(format!( - "the formal-only construct `{}` has no realization", - super::eligibility::term_key(term) - )); - } - }) - } - - /// A natural-number comparison, realized by its primitive. - fn comparison( - &mut self, - lemma: &str, - left: &SemanticTerm, - right: &SemanticTerm, - ctx: &Ctx, - ) -> Result { - let list = operands(vec![self.prove(left, ctx)?, self.prove(right, ctx)?]); - Ok(Proof { - proof: format!( - "({} {} ({} {} {}))", - lib("conv_prim"), - list.proof, - lib(lemma), - self.src(left, ctx)?, - self.src(right, ctx)? - ), - fits: and(&list.fits, TRUE), - }) - } - - fn primitive( - &mut self, - whole: &SemanticTerm, - operation: SemanticPrimitive, - arguments: &[SemanticTerm], - result: &SemanticType, - ctx: &Ctx, - ) -> Result { - let site = ctx.site.clone(); - let locals = ctx.sources(); - let types = arguments - .iter() - .map(|argument| self.source.infer(argument, &locals, &site)) - .collect::, _>>()?; - let result = self.source.close(result, &site); - match super::lower::template_of(operation, &types)? { - Some((template, instance)) => { - return self.template_call(template, &instance, arguments, &types, ctx); - } - None => {} - } - match operation { - SemanticPrimitive::MapEntries | SemanticPrimitive::SetElements => { - if arguments.len() != 1 { - return Err(format!("{operation:?} takes one operand")); + | SemanticDeclaration::Evidence { + name: _, + type_parameters: _, + contract: _, + realization: _, + claims: _, + axioms: _, } - return self.prove(&arguments[0], ctx); - } - // A key comparison is `compare` and a match on its order; the - // source's `lessThan` unfolds to the key order by its clauses. - SemanticPrimitive::LessThan => { - let key = types.first().ok_or("a key comparison has no operand")?; - let coll = format!("{}.LexLeanCollections", self.lean_module(&site.module)?); - let less = format!("{coll}.lessThan"); - let list = operands(self.proofs(arguments, ctx)?); - return Ok(Proof { - proof: format!( - "({} {} {less} {} {} {} {})", - lib("conv_lessThan"), - self.key_spec(key, &coll)?, - clause(2, &less), - clause(2, &less), - clause(2, &less), - list.proof - ), - fits: list.fits, - }); - } - SemanticPrimitive::Subtract - | SemanticPrimitive::Multiply - | SemanticPrimitive::Quotient - | SemanticPrimitive::Remainder - | SemanticPrimitive::Negate - | SemanticPrimitive::CheckedConvert - | SemanticPrimitive::CheckedAdd - | SemanticPrimitive::CheckedSubtract - | SemanticPrimitive::CheckedMultiply - | SemanticPrimitive::CheckedNegate - | SemanticPrimitive::CheckedQuotient - | SemanticPrimitive::BitAnd - | SemanticPrimitive::BitOr - | SemanticPrimitive::BitXor - | SemanticPrimitive::BitNot - | SemanticPrimitive::ShiftLeft - | SemanticPrimitive::ShiftRight - | SemanticPrimitive::Append - | SemanticPrimitive::Length - | SemanticPrimitive::Index - | SemanticPrimitive::Slice - | SemanticPrimitive::Utf8Encode - | SemanticPrimitive::Utf8Decode - | SemanticPrimitive::CompareBytes - | SemanticPrimitive::Equal - | SemanticPrimitive::SplitExact - | SemanticPrimitive::Join - | SemanticPrimitive::ParseDecimal - | SemanticPrimitive::FormatDecimal - | SemanticPrimitive::MapSize - | SemanticPrimitive::SetSize - | SemanticPrimitive::MapInsert - | SemanticPrimitive::MapRemove - | SemanticPrimitive::MapLookup - | SemanticPrimitive::MapContains - | SemanticPrimitive::MapKeys - | SemanticPrimitive::MapValues - | SemanticPrimitive::MapFold - | SemanticPrimitive::SetInsert - | SemanticPrimitive::SetRemove - | SemanticPrimitive::SetContains - | SemanticPrimitive::SetUnion - | SemanticPrimitive::SetIntersection - | SemanticPrimitive::SetDifference - | SemanticPrimitive::SetFold - | SemanticPrimitive::ListFold - | SemanticPrimitive::Iterate - | SemanticPrimitive::IterateUntil - | SemanticPrimitive::GraphSuccessors - | SemanticPrimitive::GraphReachable - | SemanticPrimitive::GraphTopological => {} - } - let rule = self.prim_rule(operation, &types, &result, &site)?; - let list = operands(self.proofs(arguments, ctx)?); - let sources = arguments - .iter() - .map(|argument| self.src(argument, ctx)) - .collect::, _>>()?; - let whole = self.src(whole, ctx)?; - let fits = match rule.width { - Width::Exact => TRUE.to_owned(), - Width::Nat => format!("(Nat.blt {whole} {NAT_BOUND})"), - Width::Int => format!("({} {whole})", lib("intFits")), - Width::Predicate(name) => format!("({} {})", lib(name), sources.join(" ")), - }; - let mut lemma = rule.lemma; - for argument in rule.prefix.iter().chain(&sources) { - lemma.push(' '); - lemma.push_str(argument); - } - Ok(Proof { - proof: format!("({} {} ({lemma}))", lib("conv_prim"), list.proof), - fits: and(&list.fits, &fits), - }) - } - - fn conditional( - &mut self, - whole: &SemanticTerm, - condition: &SemanticTerm, - then_value: &SemanticTerm, - else_value: &SemanticTerm, - ctx: &Ctx, - ) -> Result { - let result = self.source.infer(whole, &ctx.sources(), &ctx.site)?; - let encoder = self.enc(&result)?; - let tested = self.prove(condition, ctx)?; - let then_branch = self.prove(then_value, ctx)?; - let else_branch = self.prove(else_value, ctx)?; - let condition = self.src(condition, ctx)?; - let (then_value, else_value) = (self.src(then_value, ctx)?, self.src(else_value, ctx)?); - let hypothesis = ctx - .hypotheses - .get(&(std::ptr::from_ref(whole) as usize)) - .map(|index| hypothesis(*index)); - let select = |scrutinee: &str, on_true: &str, on_false: &str| { - match &hypothesis { - Some(name) => format!( - "(match (generalizing := false) {name} : {scrutinee} with | true => {on_true} | false => {on_false})" - ), - None => format!("(if {scrutinee} then {on_true} else {on_false})"), - } - }; - let binder = hypothesis.clone().unwrap_or_else(|| "_".to_owned()); - Ok(Proof { - proof: format!( - "({} (fun (__b : Bool) => {}) (fun (__b : Bool) => {encoder} {}) {condition} {} (fun {binder} => {}) (fun {binder} => {}))", - lib("conv_cond"), - select("__b", &then_branch.fits, &else_branch.fits), - select("__b", &then_value, &else_value), - tested.proof, - then_branch.proof, - else_branch.proof - ), - fits: and( - &tested.fits, - &select(&condition, &then_branch.fits, &else_branch.fits), - ), - }) - } - - fn matching( - &mut self, - whole: &SemanticTerm, - scrutinee: &SemanticTerm, - branches: &[SemanticBranch], - ctx: &Ctx, - ) -> Result { - let site = ctx.site.clone(); - let scrutinee_ty = self.source.infer(scrutinee, &ctx.sources(), &site)?; - let result = self.source.infer(whole, &ctx.sources(), &site)?; - let (scrutinee_encoder, encoder, lean_ty, result_ty) = ( - self.enc(&scrutinee_ty)?, - self.enc(&result)?, - self.ty(&scrutinee_ty)?, - self.ty(&result)?, - ); - let tested = self.prove(scrutinee, ctx)?; - let hypothesis = ctx - .hypotheses - .get(&(std::ptr::from_ref(whole) as usize)) - .map(|index| hypothesis(*index)); - let mut fits_alternatives = Vec::new(); - let mut value_alternatives = Vec::new(); - let mut proof_arms = String::new(); - for (position, branch) in branches.iter().enumerate() { - let inner = ctx.with(self.branch_locals(branch, scrutinee, ctx)?); - let body = self.prove(&branch.body, &inner)?; - let pattern = self.pattern(branch, &site)?; - fits_alternatives.push(body.fits); - value_alternatives.push(self.src(&branch.body, &inner)?); - let (constructor, _) = self.source.branch(branch, &scrutinee_ty, &site)?; - let arm = match constructor { - // A violation's realization matches its pair, then each - // Boolean, false first (`lower::violation_match`). - Constructor::Violation(first, second) => { - let pick = |value: bool, proof: String| { - let hit = format!("({} rfl rfl {proof})", lib("convA_hit")); - if value { - format!("({} rfl {hit})", lib("convA_miss")) - } else { - hit - } - }; - let on = |proof: String| { - format!("({} {} {proof})", lib("conv_match"), Self::conv_var().proof) - }; - let inner = on(pick(second, body.proof)); - let outer = on(pick(first, inner)); - format!("({} rfl rfl {outer})", lib("convA_hit")) - } - Constructor::Bool(_) - | Constructor::Zero - | Constructor::Succ - | Constructor::Nil - | Constructor::Cons - | Constructor::OptionNone - | Constructor::OptionSome - | Constructor::Ok - | Constructor::Error - | Constructor::Document { ty: _, index: _ } => { - // The arm after `position` misses, written once and not - // by wrapping the whole arm again for each miss. - let miss = format!("({} rfl ", lib("convA_miss")); - self.within(miss.len().saturating_mul(position))?; - let mut arm = miss.repeat(position); - arm.push_str(&format!("({} rfl rfl {})", lib("convA_hit"), body.proof)); - arm.push_str(&")".repeat(position)); - arm - } - }; - match &hypothesis { - Some(name) => proof_arms.push_str(&format!(" | {pattern}, {name} => {arm}")), - None => proof_arms.push_str(&format!(" | {pattern} => {arm}")), - } - self.within(proof_arms.len())?; - } - let scrutinee_src = self.src(scrutinee, ctx)?; - let fits_at = |gen: &Self, discriminant: &str| { - gen.select( - ctx, - scrutinee, - discriminant, - branches, - &fits_alternatives, - "Bool", - hypothesis.as_deref(), - ) - }; - let value_at = |gen: &Self, discriminant: &str| { - gen.select( - ctx, - scrutinee, - discriminant, - branches, - &value_alternatives, - &result_ty, - hypothesis.as_deref(), - ) - }; - let cases = match &hypothesis { - Some(name) => { - format!("(fun (__a : {lean_ty}) {name} => match __a, {name} with{proof_arms})") - } - None => format!("(fun (__a : {lean_ty}) _ => match __a with{proof_arms})"), - }; - Ok(Proof { - proof: format!( - "({} {scrutinee_encoder} (fun (__a : {lean_ty}) => {}) (fun (__a : {lean_ty}) => {encoder} {}) {scrutinee_src} {} {cases})", - lib("conv_matchV"), - fits_at(self, "__a")?, - value_at(self, "__a")?, - tested.proof - ), - fits: and(&tested.fits, &fits_at(self, &scrutinee_src)?), - }) - } - - fn application( - &mut self, - function: &SemanticTerm, - arguments: &[SemanticTerm], - ctx: &Ctx, - ) -> Result { - // The function is evaluated before its arguments (§17.14). - let (target, relation, fits) = match function { - SemanticTerm::Var { name } => { - let companion = ctx - .lookup(name)? - .companion - .clone() - .ok_or_else(|| format!("applied `{name}` has no companion"))?; - (Self::conv_var(), companion.relation, companion.fits) - } - SemanticTerm::Lambda { - parameters: _, - captures, - body: _, - } => { - let index = self.next_lambda(ctx.function)?; - let mut fits = format!("__fits_{index}"); - let mut relation = format!("__rel_{index}"); - let mut proofs = Vec::new(); - for capture in captures { - let local = ctx.lookup(capture)?; - let name = identifier(capture); - match &local.companion { - Some(companion) => { - fits.push_str(&format!(" ({name}) ({})", companion.fits)); - relation.push_str(&format!( - " ({name}) ({}) ({}) ({}) ({})", - companion.fits, - companion.index, - companion.captures, - companion.relation - )); - } - None => { - fits.push_str(&format!(" ({name})")); - relation.push_str(&format!(" ({name})")); - } - } - proofs.push(Self::conv_var()); - } - let captured = operands(proofs); - ( - Proof { - proof: format!("({} {})", lib("conv_closure"), captured.proof), - fits: captured.fits, - }, - format!("({relation})"), - format!("({fits})"), - ) - } - SemanticTerm::FunctionRef { - function, - type_arguments, - } => { - let module = Source::module_of(function, &ctx.site); - let closed: Vec = type_arguments - .iter() - .map(|argument| self.source.close(argument, &ctx.site)) - .collect(); - let index = self.instance_index(&module, &function.name, &closed)?; - ( - Proof { - proof: format!("({} {})", lib("conv_closure"), lib("convL_nil")), - fits: TRUE.to_owned(), - }, - format!("__rel_{index}"), - format!("__fits_{index}"), - ) - } - SemanticTerm::Nat { value: _ } - | SemanticTerm::Integer { - representation: _, - value: _, - } - | SemanticTerm::String { value: _ } - | SemanticTerm::Bytes { hex: _ } - | SemanticTerm::Primitive { - operation: _, - arguments: _, - result: _, - } - | SemanticTerm::Bool { value: _ } - | SemanticTerm::Unit - | SemanticTerm::Nil { element: _ } - | SemanticTerm::Cons { head: _, tail: _ } - | SemanticTerm::Record { - r#type: _, - type_arguments: _, - fields: _, - } - | SemanticTerm::Constructor { - constructor: _, - type_arguments: _, - arguments: _, - } - | SemanticTerm::InstanceValue { - class: _, - arguments: _, - resolved: _, - } - | SemanticTerm::Project { value: _, field: _ } - | SemanticTerm::Call { - function: _, - type_arguments: _, - arguments: _, - } - | SemanticTerm::If { - condition: _, - then_value: _, - else_value: _, - } - | SemanticTerm::Match { - scrutinee: _, - branches: _, - } - | SemanticTerm::Eq { left: _, right: _ } - | SemanticTerm::Le { left: _, right: _ } - | SemanticTerm::Lt { left: _, right: _ } - | SemanticTerm::Add { left: _, right: _ } - | SemanticTerm::Beq { left: _, right: _ } - | SemanticTerm::Ble { left: _, right: _ } - | SemanticTerm::Blt { left: _, right: _ } - | SemanticTerm::And { left: _, right: _ } - | SemanticTerm::PropAnd { left: _, right: _ } - | SemanticTerm::Or { left: _, right: _ } - | SemanticTerm::Not { value: _ } - | SemanticTerm::Implies { - premise: _, - conclusion: _, - } - | SemanticTerm::Iff { left: _, right: _ } - | SemanticTerm::Forall { binder: _, body: _ } - | SemanticTerm::Let { - binder: _, - value: _, - body: _, - } - | SemanticTerm::Pair { left: _, right: _ } - | SemanticTerm::First { value: _ } - | SemanticTerm::Second { value: _ } - | SemanticTerm::Apply { - function: _, - arguments: _, - } - | SemanticTerm::MapLiteral { - key: _, - value: _, - entries: _, - } - | SemanticTerm::SetLiteral { - element: _, - elements: _, - } - | SemanticTerm::GraphLiteral { - node: _, - nodes: _, - edges: _, - } - | SemanticTerm::CheckedApply { - model: _, - type_arguments: _, - arguments: _, - checks: _, - } => { - return Err(format!( - "the applied `{}` is neither a parameter, a lambda, nor a function reference", - super::eligibility::term_key(function) - )); - } - }; - let list = operands(self.proofs(arguments, ctx)?); - let sources = self.src_terms(arguments, ctx)?; - Ok(Proof { - proof: format!( - "({} {} {} ({relation}{sources}))", - lib("conv_apply"), - target.proof, - list.proof - ), - fits: and( - &target.fits, - &and(&list.fits, &format!("({fits}{sources})")), - ), - }) - } -} - -/// The binders, arguments, and statement pieces of one function's relation. -struct Signature { - /// Binders of the fits predicate. - fits_binders: Vec, - /// Binders of the relation theorem. - rel_binders: Vec, - /// The fits predicate applied to the binders. - fits_applied: String, - /// The encoded argument list the function is called with. - arguments: String, - /// The encoded denotation of the call. - value: String, - /// The pattern binder names, one per binder, for equations. - names: Vec, -} - -impl Gen<'_> { - /// A scope binding `locals` in order, function-typed ones with companions - /// named by `stem` and position. - fn scope(locals: &[(Local, SemanticType)], stem: &str) -> Vec { - locals - .iter() - .enumerate() - .map(|(position, (local, generic))| CLocal { - local: local.clone(), - generic: generic.clone(), - companion: function_type(&local.ty) - .map(|_| companion_names(&format!("{stem}{position}"))), - }) - .collect() - } - - /// The binders and arguments of a function over `locals`, whose - /// denotation applied to them is `denotation`. - fn signature( - &self, - index: u64, - locals: &[CLocal], - result: &SemanticType, - denotation: &str, - ) -> Result { - let mut fits_binders = Vec::new(); - let mut rel_binders = Vec::new(); - let mut fits_applied = format!("__fits_{index}"); - let mut arguments = Vec::new(); - let mut names = Vec::new(); - for local in locals { - let name = identifier(&local.local.name); - let ty = self.ty(&local.local.ty)?; - fits_binders.push(format!("({name} : {ty})")); - rel_binders.push(format!("({name} : {ty})")); - fits_applied.push_str(&format!(" {name}")); - names.push(name.clone()); - match &local.companion { - Some(companion) => { - let (parameters, codomain) = - function_type(&local.local.ty).ok_or("a companion of a non-function")?; - let mut domain = String::new(); - let mut binders = String::new(); - let mut applied = String::new(); - let mut encoded = Vec::new(); - for (position, parameter) in parameters.iter().enumerate() { - let lean = self.ty(parameter)?; - domain.push_str(&format!("{lean} -> ")); - binders.push_str(&format!(" (__p{position} : {lean})")); - applied.push_str(&format!(" __p{position}")); - encoded.push(format!("({} __p{position})", self.enc(parameter)?)); - } - fits_binders.push(format!("({} : {domain}Bool)", companion.fits)); - rel_binders.push(format!("({} : {domain}Bool)", companion.fits)); - rel_binders.push(format!("({} : Nat)", companion.index)); - rel_binders.push(format!("({} : List {SYNTAX}.Value)", companion.captures)); - rel_binders.push(format!( - "({} : ∀{binders}, {} __prog {} ({SEMANTICS}.LexLeanRuntime.append {} {}) ({} ({}{applied}) ({} ({name}{applied}))))", - companion.relation, - lib("FunRel"), - companion.index, - companion.captures, - list(encoded), - lib("Rel"), - companion.fits, - self.enc(codomain)? - )); - fits_applied.push_str(&format!(" {}", companion.fits)); - names.push(companion.fits.clone()); - names.push(companion.index.clone()); - names.push(companion.captures.clone()); - names.push(companion.relation.clone()); - } - None => {} - } - arguments.push(self.encoded_local(local)?); - } - Ok(Signature { - fits_binders, - rel_binders, - fits_applied, - arguments: list(arguments), - value: format!("({} ({denotation}))", self.enc(result)?), - names, - }) - } - - fn statement(&self, index: u64, signature: &Signature) -> String { - format!( - "{} __prog {index} {} ({} ({}) {})", - lib("FunRel"), - signature.arguments, - lib("Rel"), - signature.fits_applied, - signature.value - ) - } -} - -/// Strongly connected components of `graph` in dependency-first order. -fn components(graph: &BTreeMap>) -> Vec> { - struct State<'g> { - graph: &'g BTreeMap>, - index: BTreeMap, - low: BTreeMap, - stack: Vec, - on_stack: BTreeSet, - next: usize, - out: Vec>, - } - fn visit(state: &mut State<'_>, node: u64) { - state.index.insert(node, state.next); - state.low.insert(node, state.next); - state.next += 1; - state.stack.push(node); - state.on_stack.insert(node); - let successors: Vec = state - .graph - .get(&node) - .map(|edges| edges.iter().copied().collect()) - .unwrap_or_default(); - for successor in successors { - if !state.graph.contains_key(&successor) { - continue; - } - match state.index.get(&successor).copied() { - None => { - visit(state, successor); - let low = state.low[&node].min(state.low[&successor]); - state.low.insert(node, low); - } - Some(index) => { - if state.on_stack.contains(&successor) { - let low = state.low[&node].min(index); - state.low.insert(node, low); - } - } - } - } - if state.low[&node] == state.index[&node] { - let mut component = Vec::new(); - loop { - let member = state.stack.pop().expect("the node is on the stack"); - state.on_stack.remove(&member); - component.push(member); - if member == node { - break; - } - } - component.sort_unstable(); - state.out.push(component); - } - } - let mut state = State { - graph, - index: BTreeMap::new(), - low: BTreeMap::new(), - stack: Vec::new(), - on_stack: BTreeSet::new(), - next: 0, - out: Vec::new(), - }; - for node in graph.keys() { - if !state.index.contains_key(node) { - visit(&mut state, *node); - } - } - state.out -} - -impl<'a> Gen<'a> { - /// The hypothesis numbering of a well-founded definition body. - fn hypotheses( - name: &str, - group: &BTreeSet, - type_parameters: &[String], - parameters: &[SemanticParameter], - body: &SemanticTerm, - ) -> Result { - let plan = - crate::ir::semantic::well_founded_plan(name, group, type_parameters, parameters, body)?; - Ok(plan - .addresses - .iter() - .enumerate() - .map(|(index, address)| (*address, index)) - .collect()) - } - - /// The well-founded group of a definition: itself, or every member of - /// its mutual label. - fn group(&self, module: &str, name: &str, mutual: Option<&String>) -> BTreeSet { - let mut group = BTreeSet::from([name.to_owned()]); - let label = match mutual { - Some(label) => label, - None => return group, - }; - for declaration in self - .source - .modules - .get(module) - .map(|linked| linked.semantic.lowered_declarations()) - .unwrap_or_default() - { - match declaration { - SemanticDeclaration::Definition { - name, - type_parameters: _, - parameters: _, - result: _, - recursive_argument: _, - body: _, - axioms: _, - executable: _, - mutual: Some(other), - termination: _, - production: _, - } => { - if other == label { - group.insert(name.clone()); - } - } - SemanticDeclaration::Definition { - name: _, - type_parameters: _, - parameters: _, - result: _, - recursive_argument: _, - body: _, - axioms: _, - executable: _, - mutual: None, - termination: _, - production: _, - } - | SemanticDeclaration::Structure { - name: _, - type_parameters: _, - parameters: _, - fields: _, - } - | SemanticDeclaration::Class { - name: _, - type_parameters: _, - parameters: _, - fields: _, - } - | SemanticDeclaration::Instance { - name: _, - class: _, - arguments: _, - priority: _, - fields: _, - } - | SemanticDeclaration::Inductive { - name: _, - type_parameters: _, - parameters: _, - constructors: _, - mutual: _, - } - | SemanticDeclaration::Theorem { - name: _, - type_parameters: _, - parameters: _, - statement: _, - proof: _, - axioms: _, - } - | SemanticDeclaration::Artifact { - name: _, - role: _, - sha256: _, - length: _, - schema: _, - r#type: _, - axioms: _, - } - | SemanticDeclaration::Contract { - name: _, - type_parameters: _, - input: _, - output: _, - state: _, - precondition: _, - postcondition: _, - invariant: _, - validators: _, - axioms: _, - } - | SemanticDeclaration::Realization { - name: _, - type_parameters: _, - input: _, - output: _, - state: _, - descriptor: _, - executable: _, - axioms: _, - } - | SemanticDeclaration::Evidence { - name: _, - type_parameters: _, - contract: _, - realization: _, - claims: _, - axioms: _, - } - | SemanticDeclaration::Model { - name: _, - type_parameters: _, - contract: _, - realization: _, - evidence: _, - entry: _, - axioms: _, - } - | SemanticDeclaration::Logic { - name: _, - type_parameters: _, - state: _, - relation: _, - invariant: _, - ranking: _, - axioms: _, - } - | SemanticDeclaration::InferenceRule { - name: _, - type_parameters: _, - logic: _, - binding: _, - guard: _, - conclusion: _, - soundness: _, - progress: _, - executable: _, - axioms: _, - } - | SemanticDeclaration::Verifier { - name: _, - type_parameters: _, - subject: _, - candidate: _, - specification: _, - check: _, - sound: _, - complete: _, - axioms: _, - } - | SemanticDeclaration::Reasoner { - name: _, - type_parameters: _, - logic: _, - observation: _, - observe: _, - rules: _, - strategy: _, - answer: _, - verifier: _, - claims: _, - executable: _, - axioms: _, - } => {} - } - } - group - } - - /// Record every function the certificate relates and what each one's - /// relation depends on. - #[allow(clippy::too_many_lines)] - fn collect(&mut self) -> Result>, String> { - let mut graph: BTreeMap> = BTreeMap::new(); - let origins: Vec<(u64, Origin)> = self - .layout - .functions - .iter() - .enumerate() - .map(|(index, origin)| (index as u64, origin.clone())) - .collect(); - let mut counters: BTreeMap = BTreeMap::new(); - for (index, origin) in origins { - match origin { - Origin::Definition { - module, - name, - type_arguments, - instance: _, - } => { - let declaration = self.source.declaration(&module, &name); - let ( - type_parameters, - parameters, - body, - recursive_argument, - mutual, - termination, - ) = match declaration { - Some(SemanticDeclaration::Definition { - name: _, - type_parameters, - parameters, - result: _, - recursive_argument, - body, - axioms: _, - executable: _, - mutual, - termination, - production: _, - }) => ( - type_parameters, - parameters, - body, - recursive_argument, - mutual, - termination, - ), - Some( - SemanticDeclaration::Structure { - name: _, - type_parameters: _, - parameters: _, - fields: _, - } - | SemanticDeclaration::Class { - name: _, - type_parameters: _, - parameters: _, - fields: _, - } - | SemanticDeclaration::Instance { - name: _, - class: _, - arguments: _, - priority: _, - fields: _, - } - | SemanticDeclaration::Inductive { - name: _, - type_parameters: _, - parameters: _, - constructors: _, - mutual: _, - } - | SemanticDeclaration::Theorem { - name: _, - type_parameters: _, - parameters: _, - statement: _, - proof: _, - axioms: _, - } - | SemanticDeclaration::Artifact { - name: _, - role: _, - sha256: _, - length: _, - schema: _, - r#type: _, - axioms: _, - } - | SemanticDeclaration::Contract { - name: _, - type_parameters: _, - input: _, - output: _, - state: _, - precondition: _, - postcondition: _, - invariant: _, - validators: _, - axioms: _, - } - | SemanticDeclaration::Realization { - name: _, - type_parameters: _, - input: _, - output: _, - state: _, - descriptor: _, - executable: _, - axioms: _, - } - | SemanticDeclaration::Evidence { - name: _, - type_parameters: _, - contract: _, - realization: _, - claims: _, - axioms: _, - } - | SemanticDeclaration::Model { - name: _, - type_parameters: _, - contract: _, - realization: _, - evidence: _, - entry: _, - axioms: _, - } - | SemanticDeclaration::Logic { - name: _, - type_parameters: _, - state: _, - relation: _, - invariant: _, - ranking: _, - axioms: _, - } - | SemanticDeclaration::InferenceRule { - name: _, - type_parameters: _, - logic: _, - binding: _, - guard: _, - conclusion: _, - soundness: _, - progress: _, - executable: _, - axioms: _, - } - | SemanticDeclaration::Verifier { - name: _, - type_parameters: _, - subject: _, - candidate: _, - specification: _, - check: _, - sound: _, - complete: _, - axioms: _, - } - | SemanticDeclaration::Reasoner { - name: _, - type_parameters: _, - logic: _, - observation: _, - observe: _, - rules: _, - strategy: _, - answer: _, - verifier: _, - claims: _, - executable: _, - axioms: _, - }, - ) - | None => return Err(format!("`{module}.{name}` is not a definition")), - }; - let site = Site { - module: module.clone(), - substitution: Source::instantiation(type_parameters, &type_arguments)?, - }; - let (closed, result) = - self.source.signature(&module, &name, &type_arguments)?; - let identity: Vec = type_parameters - .iter() - .map(|parameter| SemanticType::Parameter { - name: parameter.clone(), - }) - .collect(); - let (generic, _) = self.source.signature(&module, &name, &identity)?; - let locals: Vec<(Local, SemanticType)> = closed - .into_iter() - .zip(generic) - .map(|(closed, generic)| { - ( - Local { - name: closed.name, - ty: closed.r#type, - }, - generic.r#type, - ) - }) - .collect(); - let instance_parameters: Vec<(String, SemanticType)> = type_parameters - .iter() - .cloned() - .zip(type_arguments.iter().cloned()) - .collect(); - let (recursion, hypotheses) = match termination { - Some(termination) => { - let group = self.group(&module, &name, mutual.as_ref()); - let hypotheses = - Self::hypotheses(&name, &group, type_parameters, parameters, body)?; - ( - Recursion::WellFounded { - termination: termination.clone(), - group, - }, - hypotheses, - ) - } - None => match recursive_argument { - Some(argument) => ( - Recursion::Structural { - argument: argument.clone(), - mutual: mutual.clone(), - }, - Hypotheses::new(), - ), - None => (Recursion::None, Hypotheses::new()), - }, - }; - let ctx = Ctx::new( - site.clone(), - instance_parameters.clone(), - Self::scope(&locals, ""), - hypotheses, - index, - ); - let mut dependencies = BTreeSet::new(); - self.discover(body, &ctx, &mut dependencies, &mut counters)?; - graph.insert(index, dependencies); - self.subjects.insert( - index, - Subject::Definition { - module, - name, - type_arguments, - locals, - result, - body, - recursion, - site, - type_parameters: instance_parameters, - }, - ); - } - Origin::Instance { module, name } => { - let (class, arguments, fields) = match self.source.declaration(&module, &name) { - Some(SemanticDeclaration::Instance { - name: _, - class, - arguments, - priority: _, - fields, - }) => (class, arguments, fields), - Some( - SemanticDeclaration::Structure { - name: _, - type_parameters: _, - parameters: _, - fields: _, - } - | SemanticDeclaration::Class { - name: _, - type_parameters: _, - parameters: _, - fields: _, - } - | SemanticDeclaration::Definition { - name: _, - type_parameters: _, - parameters: _, - result: _, - recursive_argument: _, - body: _, - axioms: _, - executable: _, - mutual: _, - termination: _, - production: _, - } - | SemanticDeclaration::Inductive { - name: _, - type_parameters: _, - parameters: _, - constructors: _, - mutual: _, - } - | SemanticDeclaration::Theorem { - name: _, - type_parameters: _, - parameters: _, - statement: _, - proof: _, - axioms: _, - } - | SemanticDeclaration::Artifact { - name: _, - role: _, - sha256: _, - length: _, - schema: _, - r#type: _, - axioms: _, - } - | SemanticDeclaration::Contract { - name: _, - type_parameters: _, - input: _, - output: _, - state: _, - precondition: _, - postcondition: _, - invariant: _, - validators: _, - axioms: _, - } - | SemanticDeclaration::Realization { - name: _, - type_parameters: _, - input: _, - output: _, - state: _, - descriptor: _, - executable: _, - axioms: _, - } - | SemanticDeclaration::Evidence { - name: _, - type_parameters: _, - contract: _, - realization: _, - claims: _, - axioms: _, - } - | SemanticDeclaration::Model { - name: _, - type_parameters: _, - contract: _, - realization: _, - evidence: _, - entry: _, - axioms: _, - } - | SemanticDeclaration::Logic { - name: _, - type_parameters: _, - state: _, - relation: _, - invariant: _, - ranking: _, - axioms: _, - } - | SemanticDeclaration::InferenceRule { - name: _, - type_parameters: _, - logic: _, - binding: _, - guard: _, - conclusion: _, - soundness: _, - progress: _, - executable: _, - axioms: _, - } - | SemanticDeclaration::Verifier { - name: _, - type_parameters: _, - subject: _, - candidate: _, - specification: _, - check: _, - sound: _, - complete: _, - axioms: _, - } - | SemanticDeclaration::Reasoner { - name: _, - type_parameters: _, - logic: _, - observation: _, - observe: _, - rules: _, - strategy: _, - answer: _, - verifier: _, - claims: _, - executable: _, - axioms: _, - }, - ) - | None => return Err(format!("`{module}.{name}` is not an instance")), - }; - let site = Site { - module: module.clone(), - substitution: BTreeMap::new(), - }; - let class = self.source.close( - &SemanticType::Named { - member: class.clone(), - arguments: arguments.clone(), - }, - &site, - ); - let ctx = Ctx::new(site, Vec::new(), Vec::new(), Hypotheses::new(), index); - let mut dependencies = BTreeSet::new(); - for SemanticAssignment { field: _, value } in fields { - self.discover(value, &ctx, &mut dependencies, &mut counters)?; - } - graph.insert(index, dependencies); - self.subjects.insert( - index, - Subject::Instance { - module, - name, - class, - fields, - }, - ); - } - Origin::Lambda { - owner: _, - ordinal: _, - } - | Origin::Template { - template: _, - types: _, - entry: _, - member: _, - } - | Origin::Validator { - ty: _, - module: _, - kind: _, - } - | Origin::Entry { validators: _ } => {} - } - } - // Lambdas were registered as their owners were walked; walk each - // lambda body for its own dependencies and nested lambdas. - let mut pending: Vec = self - .subjects - .iter() - .filter_map(|(index, subject)| match subject { - Subject::Lambda { - site: _, - captures: _, - parameters: _, - body: _, - hypotheses: _, - type_parameters: _, - } => Some(*index), - Subject::Definition { - module: _, - name: _, - type_arguments: _, - locals: _, - result: _, - body: _, - recursion: _, - site: _, - type_parameters: _, - } - | Subject::Instance { - module: _, - name: _, - class: _, - fields: _, - } => None, - }) - .collect(); - loop { - let index = match pending.pop() { - Some(index) => index, - None => break, - }; - if graph.contains_key(&index) { - continue; - } - let ctx = self.lambda_ctx(index)?; - let body = match self.subjects.get(&index) { - Some(Subject::Lambda { - site: _, - captures: _, - parameters: _, - body, - hypotheses: _, - type_parameters: _, - }) => *body, - Some( - Subject::Definition { - module: _, - name: _, - type_arguments: _, - locals: _, - result: _, - body: _, - recursion: _, - site: _, - type_parameters: _, - } - | Subject::Instance { - module: _, - name: _, - class: _, - fields: _, - }, - ) - | None => return Err(format!("function {index} is not a lambda")), - }; - let before: BTreeSet = self.subjects.keys().copied().collect(); - let mut dependencies = BTreeSet::new(); - self.discover(body, &ctx, &mut dependencies, &mut counters)?; - graph.insert(index, dependencies); - pending.extend( - self.subjects - .keys() - .copied() - .filter(|key| !before.contains(key)), - ); - } - Ok(graph) - } - - /// The scope of a lambda's own function: its captures, then its - /// parameters. - fn lambda_ctx(&self, index: u64) -> Result { - match self.subjects.get(&index) { - Some(Subject::Lambda { - site, - captures, - parameters, - body: _, - hypotheses, - type_parameters, - }) => { - let mut locals = captures.clone(); - locals.extend(Self::scope(parameters, "p")); - Ok(Ctx::new( - site.clone(), - type_parameters.clone(), - locals, - hypotheses.clone(), - index, - )) - } - Some( - Subject::Definition { - module: _, - name: _, - type_arguments: _, - locals: _, - result: _, - body: _, - recursion: _, - site: _, - type_parameters: _, - } - | Subject::Instance { - module: _, - name: _, - class: _, - fields: _, - }, - ) - | None => Err(format!("function {index} is not a lambda")), - } - } - - fn discover_all( - &mut self, - terms: &'a [SemanticTerm], - ctx: &Ctx, - dependencies: &mut BTreeSet, - counters: &mut BTreeMap, - ) -> Result<(), String> { - for term in terms { - self.discover(term, ctx, dependencies, counters)?; - } - Ok(()) - } - - /// Walk a body in lowering order, recording the functions its relation - /// depends on and registering each lambda it forms. - #[allow(clippy::too_many_lines)] - fn discover( - &mut self, - term: &'a SemanticTerm, - ctx: &Ctx, - dependencies: &mut BTreeSet, - counters: &mut BTreeMap, - ) -> Result<(), String> { - let site = ctx.site.clone(); - match term { - SemanticTerm::Var { name: _ } - | SemanticTerm::Nat { value: _ } - | SemanticTerm::Integer { - representation: _, - value: _, - } - | SemanticTerm::String { value: _ } - | SemanticTerm::Bytes { hex: _ } - | SemanticTerm::Bool { value: _ } - | SemanticTerm::Unit - | SemanticTerm::Nil { element: _ } => Ok(()), - SemanticTerm::Primitive { - operation: _, - arguments, - result: _, - } - | SemanticTerm::Constructor { - constructor: _, - type_arguments: _, - arguments, - } => self.discover_all(arguments, ctx, dependencies, counters), - SemanticTerm::Cons { - head: left, - tail: right, - } - | SemanticTerm::Add { left, right } - | SemanticTerm::Beq { left, right } - | SemanticTerm::Ble { left, right } - | SemanticTerm::Blt { left, right } - | SemanticTerm::And { left, right } - | SemanticTerm::Or { left, right } - | SemanticTerm::Pair { left, right } => { - self.discover(left, ctx, dependencies, counters)?; - self.discover(right, ctx, dependencies, counters) - } - SemanticTerm::Record { - r#type: _, - type_arguments: _, - fields, - } => { - let ty = self.source.infer(term, &ctx.sources(), &site)?; - let shape = self.source.document(&ty)?; - for field in &shape.field_names { - let SemanticAssignment { field: _, value } = fields - .iter() - .find(|assignment| &assignment.field == field) - .ok_or_else(|| format!("field `{field}` is not assigned"))?; - self.discover(value, ctx, dependencies, counters)?; - } - Ok(()) - } - SemanticTerm::InstanceValue { - class: _, - arguments: _, - resolved, - } => { - let module = Source::module_of(resolved, &site); - dependencies.insert(self.instance_index(&module, &resolved.name, &[])?); - Ok(()) - } - SemanticTerm::Project { value, field: _ } - | SemanticTerm::Not { value } - | SemanticTerm::First { value } - | SemanticTerm::Second { value } => self.discover(value, ctx, dependencies, counters), - SemanticTerm::Call { - function, - type_arguments, - arguments, - } => { - let module = Source::module_of(function, &site); - let closed: Vec = type_arguments - .iter() - .map(|argument| self.source.close(argument, &site)) - .collect(); - dependencies.insert(self.instance_index(&module, &function.name, &closed)?); - self.discover_all(arguments, ctx, dependencies, counters) - } - SemanticTerm::FunctionRef { - function, - type_arguments, - } => { - let module = Source::module_of(function, &site); - let closed: Vec = type_arguments - .iter() - .map(|argument| self.source.close(argument, &site)) - .collect(); - dependencies.insert(self.instance_index(&module, &function.name, &closed)?); - Ok(()) - } - SemanticTerm::If { - condition, - then_value, - else_value, - } => { - self.discover(condition, ctx, dependencies, counters)?; - self.discover(then_value, ctx, dependencies, counters)?; - self.discover(else_value, ctx, dependencies, counters) - } - SemanticTerm::Match { - scrutinee, - branches, - } => { - self.discover(scrutinee, ctx, dependencies, counters)?; - for branch in branches { - let inner = ctx.with(self.branch_locals(branch, scrutinee, ctx)?); - self.discover(&branch.body, &inner, dependencies, counters)?; - } - Ok(()) - } - SemanticTerm::Let { - binder, - value, - body, - } => { - self.discover(value, ctx, dependencies, counters)?; - let inner = ctx.with(vec![self.let_local(binder, ctx)]); - self.discover(body, &inner, dependencies, counters) - } - SemanticTerm::Lambda { - parameters, - captures, - body, - } => { - let ordinal = counters.entry(ctx.function).or_insert(0); - let current = *ordinal; - *ordinal += 1; - let index = self - .lambdas - .get(&(ctx.function, current)) - .copied() - .ok_or_else(|| { - format!( - "lambda {current} of function {} is not in the layout", - ctx.function - ) - })?; - dependencies.insert(index); - let mut captured = Vec::new(); - for capture in captures { - let local = ctx.lookup(capture)?; - captured.push((local.local.clone(), local.generic.clone())); - } - let parameters = parameters - .iter() - .map(|binder| self.let_local(binder, ctx)) - .collect(); - self.subjects.insert( - index, - Subject::Lambda { - site: site.clone(), - captures: Self::scope(&captured, "c"), - parameters, - body, - hypotheses: ctx.hypotheses.clone(), - type_parameters: ctx.parameters.clone(), - }, - ); - Ok(()) - } - SemanticTerm::Apply { - function, - arguments, - } => { - self.discover(function, ctx, dependencies, counters)?; - self.discover_all(arguments, ctx, dependencies, counters) - } - SemanticTerm::MapLiteral { - key: _, - value: _, - entries, - } => { - for SemanticMapEntry { key, value } in entries { - self.discover(key, ctx, dependencies, counters)?; - self.discover(value, ctx, dependencies, counters)?; - } - Ok(()) - } - SemanticTerm::SetLiteral { - element: _, - elements, - } => self.discover_all(elements, ctx, dependencies, counters), - // Certificates read elaborated declarations, in which a checked - // application is the ordinary term it means (§17.12). - SemanticTerm::CheckedApply { - model: _, - type_arguments: _, - arguments: _, - checks: _, - } => Err( - "a checked model application is certified only through its elaboration".to_owned(), - ), - SemanticTerm::GraphLiteral { - node: _, - nodes, - edges, - } => { - for source in nodes { - for SemanticEdge { - source: from, - target, - } in edges - { - if from == source { - self.discover(target, ctx, dependencies, counters)?; - } - } - self.discover(source, ctx, dependencies, counters)?; - } - Ok(()) - } - SemanticTerm::Eq { left: _, right: _ } - | SemanticTerm::Le { left: _, right: _ } - | SemanticTerm::Lt { left: _, right: _ } - | SemanticTerm::PropAnd { left: _, right: _ } - | SemanticTerm::Implies { - premise: _, - conclusion: _, - } - | SemanticTerm::Iff { left: _, right: _ } - | SemanticTerm::Forall { binder: _, body: _ } => Err(format!( - "the formal-only construct `{}` reached the certificate", - super::eligibility::term_key(term) - )), - } - } -} - -/// The closed document types a type mentions. -fn named_types(ty: &SemanticType, out: &mut Vec) { - match ty { - SemanticType::Named { - member: _, - arguments, - } => { - out.push(ty.clone()); - for argument in arguments { - named_types(argument, out); - } - } - SemanticType::Option { value: inner } - | SemanticType::List { element: inner } - | SemanticType::Set { element: inner } => named_types(inner, out), - SemanticType::Result { - ok: left, - error: right, - } - | SemanticType::Product { left, right } - | SemanticType::Map { - key: left, - value: right, - } => { - named_types(left, out); - named_types(right, out); - } - SemanticType::Function { parameters, result } => { - for parameter in parameters { - named_types(parameter, out); - } - named_types(result, out); - } - SemanticType::Type - | SemanticType::Parameter { name: _ } - | SemanticType::Nat - | SemanticType::Bool - | SemanticType::Prop - | SemanticType::Unit - | SemanticType::Int - | SemanticType::Int8 - | SemanticType::Int16 - | SemanticType::Int32 - | SemanticType::Int64 - | SemanticType::UInt8 - | SemanticType::UInt16 - | SemanticType::UInt32 - | SemanticType::UInt64 - | SemanticType::String - | SemanticType::Bytes - | SemanticType::Ordering - | SemanticType::ContractViolation - | SemanticType::ReasoningFailure => {} - } -} - -/// The encoder of a nested container outside its group's mutual block. -fn nested_enc(nested: &Nested) -> String { - match &nested.ty { - SemanticType::List { element: _ } - | SemanticType::Set { element: _ } - | SemanticType::Map { key: _, value: _ } - | SemanticType::ContractViolation - | SemanticType::ReasoningFailure => { - format!("({} __L_{})", lib("ListEnc.enc"), nested.index) - } - SemanticType::Option { value: _ } => { - format!("({} __O_{})", lib("OptEnc.enc"), nested.index) - } - SemanticType::Result { ok: _, error: _ } - | SemanticType::Product { left: _, right: _ } - | SemanticType::Named { - member: _, - arguments: _, - } - | SemanticType::Function { - parameters: _, - result: _, - } - | SemanticType::Type - | SemanticType::Prop - | SemanticType::Parameter { name: _ } - | SemanticType::Nat - | SemanticType::Bool - | SemanticType::Unit - | SemanticType::Int - | SemanticType::Int8 - | SemanticType::Int16 - | SemanticType::Int32 - | SemanticType::Int64 - | SemanticType::UInt8 - | SemanticType::UInt16 - | SemanticType::UInt32 - | SemanticType::UInt64 - | SemanticType::String - | SemanticType::Bytes - | SemanticType::Ordering => format!("__aux_{}", nested.index), - } -} - -fn mutual_block(items: &[String]) -> String { - if items.len() == 1 { - return items[0].clone(); - } - format!("mutual\n{}end\n\n", items.concat()) -} - -impl<'a> Gen<'a> { - /// The dependency graph of the program's document types: each ADT points - /// at the ADTs its fields mention. - fn adt_graph(&self) -> Result>, String> { - let mut graph: BTreeMap> = BTreeMap::new(); - for (index, adt) in self.layout.adts.iter().enumerate() { - let shape = self.source.document(&adt.ty)?; - let mut dependencies = BTreeSet::new(); - for fields in &shape.fields { - for field in fields { - let mut named = Vec::new(); - named_types(field, &mut named); - for ty in named { - let text = self.source.type_text(&ty); - dependencies.insert( - *self - .adts - .get(&text) - .ok_or_else(|| format!("`{text}` has no ADT in the layout"))?, - ); - } - } - } - graph.insert(index as u64, dependencies); - } - Ok(graph) - } - - /// Register every container type nested inside a recursive group of - /// document types. - fn nest(&mut self) -> Result<(), String> { - let graph = self.adt_graph()?; - for component in components(&graph) { - let members: BTreeSet = component.iter().copied().collect(); - let recursive = component.len() > 1 - || component - .iter() - .any(|index| graph.get(index).is_some_and(|edges| edges.contains(index))); - if !recursive { - continue; - } - for index in &component { - let ty = self.layout.adts[*index as usize].ty.clone(); - let shape = self.source.document(&ty)?; - for fields in &shape.fields { - for field in fields { - self.register(field, &members, component[0])?; - } - } - } - } - Ok(()) - } - - /// Register `ty` and its nested containers when it mentions a member of - /// the group; inner containers first, so indices follow dependencies. - fn register( - &mut self, - ty: &SemanticType, - members: &BTreeSet, - group: u64, - ) -> Result<(), String> { - let mut named = Vec::new(); - named_types(ty, &mut named); - let touches = named.iter().any(|named| { - self.adts - .get(&self.source.type_text(named)) - .is_some_and(|index| members.contains(index)) - }); - if !touches { - return Ok(()); - } - match ty { - SemanticType::Named { - member: _, - arguments: _, - } => return Ok(()), - SemanticType::Option { value: inner } - | SemanticType::List { element: inner } - | SemanticType::Set { element: inner } => self.register(inner, members, group)?, - SemanticType::Map { key, value } => self.register( - &SemanticType::Product { - left: key.clone(), - right: value.clone(), - }, - members, - group, - )?, - SemanticType::Result { - ok: left, - error: right, - } - | SemanticType::Product { left, right } => { - self.register(left, members, group)?; - self.register(right, members, group)?; - } - SemanticType::Function { - parameters: _, - result: _, - } - | SemanticType::Type - | SemanticType::Prop - | SemanticType::Parameter { name: _ } - | SemanticType::Nat - | SemanticType::Bool - | SemanticType::Unit - | SemanticType::Int - | SemanticType::Int8 - | SemanticType::Int16 - | SemanticType::Int32 - | SemanticType::Int64 - | SemanticType::UInt8 - | SemanticType::UInt16 - | SemanticType::UInt32 - | SemanticType::UInt64 - | SemanticType::String - | SemanticType::Bytes - | SemanticType::Ordering - | SemanticType::ContractViolation - | SemanticType::ReasoningFailure => { - return Err(format!( - "`{}` cannot hold a document type in a field", - self.source.type_text(ty) - )); - } - } - let text = self.source.type_text(ty); - let index = self.nested.len() as u64; - self.nested.entry(text).or_insert(Nested { - index, - ty: ty.clone(), - group, - }); - Ok(()) - } - - /// `arg` encoded inside a recursive group's mutual block, where a nested - /// container's bundle is not yet defined and its auxiliary function is - /// applied directly, as structural recursion requires. - fn raw_apply(&self, ty: &SemanticType, arg: &str) -> Result { - match self.nested.get(&self.source.type_text(ty)) { - Some(nested) => Ok(match &nested.ty { - SemanticType::List { element: _ } - | SemanticType::Set { element: _ } - | SemanticType::Map { key: _, value: _ } - | SemanticType::ContractViolation - | SemanticType::ReasoningFailure => { - format!("({} (__items_{} {arg}))", value_ctor("list"), nested.index) - } - SemanticType::Option { value: _ } - | SemanticType::Result { ok: _, error: _ } - | SemanticType::Product { left: _, right: _ } - | SemanticType::Named { - member: _, - arguments: _, - } - | SemanticType::Function { - parameters: _, - result: _, - } - | SemanticType::Type - | SemanticType::Prop - | SemanticType::Parameter { name: _ } - | SemanticType::Nat - | SemanticType::Bool - | SemanticType::Unit - | SemanticType::Int - | SemanticType::Int8 - | SemanticType::Int16 - | SemanticType::Int32 - | SemanticType::Int64 - | SemanticType::UInt8 - | SemanticType::UInt16 - | SemanticType::UInt32 - | SemanticType::UInt64 - | SemanticType::String - | SemanticType::Bytes - | SemanticType::Ordering => format!("(__aux_{} {arg})", nested.index), - }), - None => Ok(format!("({} {arg})", self.enc(ty)?)), - } - } - - /// The list encoder bundle of a list, set or map type. - fn list_bundle(&self, ty: &SemanticType) -> Result { - match self.nested.get(&self.source.type_text(ty)) { - Some(nested) => Ok(format!("__L_{}", nested.index)), - None => Ok(format!( - "({} {})", - lib("listEnc"), - self.enc(&element_of(ty)?)? - )), - } - } - - /// The option encoder bundle of an option type. - fn option_bundle(&self, ty: &SemanticType) -> Result { - match self.nested.get(&self.source.type_text(ty)) { - Some(nested) => Ok(format!("__O_{}", nested.index)), - None => Ok(format!( - "({} {})", - lib("optEnc"), - self.enc(&option_value(ty)?)? - )), - } - } - - /// The auxiliary function of a nested container, and its bundle when the - /// container is a list or an option. - fn auxiliary(&self, nested: &Nested) -> Result<(String, String), String> { - let lean = self.ty(&nested.ty)?; - let n = nested.index; - let value = format!("{SYNTAX}.Value"); - Ok(match &nested.ty { - SemanticType::List { element: _ } - | SemanticType::Set { element: _ } - | SemanticType::Map { key: _, value: _ } - | SemanticType::ContractViolation | SemanticType::ReasoningFailure => { - let element = element_of(&nested.ty)?; - ( - format!( - "def __items_{n} : {lean} -> List {value}\n | [] => []\n | __x0 :: __x1 => {} :: __items_{n} __x1\n\n", - self.raw_apply(&element, "__x0")? - ), - format!( - "def __L_{n} : {} {} :=\n ⟨{}, __items_{n}, __items_{n}.eq_1, __items_{n}.eq_2⟩\n\n", - lib("ListEnc"), - self.ty(&element)?, - self.enc(&element)? - ), - ) - } - SemanticType::Option { value: inner } => ( - format!( - "def __aux_{n} : {lean} -> {value}\n | none => {}\n | some __x0 => {} {}\n\n", - value_ctor("none"), - value_ctor("some"), - self.raw_apply(inner, "__x0")? - ), - format!( - "def __O_{n} : {} {} :=\n ⟨{}, __aux_{n}, __aux_{n}.eq_1, __aux_{n}.eq_2⟩\n\n", - lib("OptEnc"), - self.ty(inner)?, - self.enc(inner)? - ), - ), - SemanticType::Product { left, right } => ( - format!( - "def __aux_{n} : {lean} -> {value}\n | (__x0, __x1) => {} {} {}\n\n", - value_ctor("pair"), - self.raw_apply(left, "__x0")?, - self.raw_apply(right, "__x1")? - ), - String::new(), - ), - SemanticType::Result { ok, error } => ( - format!( - "def __aux_{n} : {lean} -> {value}\n | Except.error __x0 => {} {}\n | Except.ok __x0 => {} {}\n\n", - value_ctor("error"), - self.raw_apply(error, "__x0")?, - value_ctor("ok"), - self.raw_apply(ok, "__x0")? - ), - String::new(), - ), - SemanticType::Named { - member: _, - arguments: _, - } - | SemanticType::Function { - parameters: _, - result: _, - } - | SemanticType::Type - | SemanticType::Prop - | SemanticType::Parameter { name: _ } - | SemanticType::Nat - | SemanticType::Bool - | SemanticType::Unit - | SemanticType::Int - | SemanticType::Int8 - | SemanticType::Int16 - | SemanticType::Int32 - | SemanticType::Int64 - | SemanticType::UInt8 - | SemanticType::UInt16 - | SemanticType::UInt32 - | SemanticType::UInt64 - | SemanticType::String - | SemanticType::Bytes - | SemanticType::Ordering => { - return Err(format!( - "`{}` is not a nested container", - self.source.type_text(&nested.ty) - )); - } - }) - } - - /// The encoder definitions of every document type the program realizes, - /// dependencies first; a mutual group of types gets one mutual block, - /// joined by the auxiliary encoders of the containers nested in it. - fn encoders(&self) -> Result { - let graph = self.adt_graph()?; - let mut texts: BTreeMap = BTreeMap::new(); - for (index, adt) in self.layout.adts.iter().enumerate() { - let index = index as u64; - let shape = self.source.document(&adt.ty)?; - let lean = self.ty(&adt.ty)?; - let recursive = self.nested.values().any(|nested| nested.group == index) - || components(&graph) - .iter() - .any(|component| component.len() > 1 && component.contains(&index)) - || graph - .get(&index) - .is_some_and(|edges| edges.contains(&index)); - let text = if shape.kind == "inductive" || recursive { - let owner = self.ty(&adt.ty)?; - let owner = owner - .trim_start_matches('(') - .split(' ') - .next() - .unwrap_or_default() - .trim_end_matches(')') - .to_owned(); - let mut text = format!("def __enc_{index} : {lean} -> {SYNTAX}.Value\n"); - for (position, (constructor, fields)) in - shape.names.iter().zip(&shape.fields).enumerate() - { - let binders: Vec = (0..fields.len()) - .map(|field| format!("__x{field}")) - .collect(); - let encoded = fields - .iter() - .zip(&binders) - .map(|(field, binder)| self.raw_apply(field, binder)) - .collect::, _>>()?; - let pattern = if shape.kind == "inductive" { - format!( - "{owner}.{}{}", - identifier(constructor), - binders - .iter() - .map(|binder| format!(" {binder}")) - .collect::() - ) - } else { - format!("⟨{}⟩", binders.join(", ")) - }; - text.push_str(&format!( - " | {pattern} => {} {position} {}\n", - value_ctor("adt"), - list(encoded) - )); + | SemanticDeclaration::Model { + name: _, + type_parameters: _, + contract: _, + realization: _, + evidence: _, + entry: _, + axioms: _, } - text.push('\n'); - text - } else { - let fields = shape - .field_names - .iter() - .zip(&shape.fields[0]) - .map(|(name, ty)| { - Ok::(format!( - "({} (__s).{})", - self.enc(ty)?, - identifier(name) - )) - }) - .collect::, _>>()?; - format!( - "def __enc_{index} (__s : {lean}) : {SYNTAX}.Value :=\n {} 0 {}\n\n", - value_ctor("adt"), - list(fields) - ) - }; - texts.insert(index, text); - } - let mut out = String::new(); - for component in components(&graph) { - let mut items: Vec = - component.iter().map(|index| texts[index].clone()).collect(); - let mut bundles = String::new(); - let mut nested: Vec<&Nested> = self - .nested - .values() - .filter(|nested| nested.group == component[0]) - .collect(); - nested.sort_by_key(|nested| nested.index); - for nested in nested { - let (auxiliary, bundle) = self.auxiliary(nested)?; - items.push(auxiliary); - bundles.push_str(&bundle); - } - out.push_str(&mutual_block(&items)); - out.push_str(&bundles); - } - Ok(out) - } - - /// The decreasing proof of a well-founded instance: exactly the source's - /// evidence applications at the instance's types. - fn decreasing( - &self, - module: &str, - name: &str, - type_arguments: &[SemanticType], - group: &BTreeSet, - ) -> Result { - let (type_parameters, parameters, body, termination) = - match self.source.declaration(module, name) { - Some(SemanticDeclaration::Definition { + | SemanticDeclaration::Logic { name: _, - type_parameters, - parameters, - result: _, - recursive_argument: _, - body, + type_parameters: _, + state: _, + relation: _, + invariant: _, + ranking: _, axioms: _, - executable: _, - mutual: _, - termination: Some(termination), - production: _, - }) => (type_parameters, parameters, body, termination), - Some( - SemanticDeclaration::Definition { - name: _, - type_parameters: _, - parameters: _, - result: _, - recursive_argument: _, - body: _, - axioms: _, - executable: _, - mutual: _, - termination: None, - production: _, - } - | SemanticDeclaration::Structure { - name: _, - type_parameters: _, - parameters: _, - fields: _, - } - | SemanticDeclaration::Class { - name: _, - type_parameters: _, - parameters: _, - fields: _, - } - | SemanticDeclaration::Instance { - name: _, - class: _, - arguments: _, - priority: _, - fields: _, - } - | SemanticDeclaration::Inductive { - name: _, - type_parameters: _, - parameters: _, - constructors: _, - mutual: _, - } - | SemanticDeclaration::Theorem { - name: _, - type_parameters: _, - parameters: _, - statement: _, - proof: _, - axioms: _, - } - | SemanticDeclaration::Artifact { - name: _, - role: _, - sha256: _, - length: _, - schema: _, - r#type: _, - axioms: _, - } - | SemanticDeclaration::Contract { - name: _, - type_parameters: _, - input: _, - output: _, - state: _, - precondition: _, - postcondition: _, - invariant: _, - validators: _, - axioms: _, - } - | SemanticDeclaration::Realization { - name: _, - type_parameters: _, - input: _, - output: _, - state: _, - descriptor: _, - executable: _, - axioms: _, - } - | SemanticDeclaration::Evidence { - name: _, - type_parameters: _, - contract: _, - realization: _, - claims: _, - axioms: _, - } - | SemanticDeclaration::Model { - name: _, - type_parameters: _, - contract: _, - realization: _, - evidence: _, - entry: _, - axioms: _, - } - | SemanticDeclaration::Logic { - name: _, - type_parameters: _, - state: _, - relation: _, - invariant: _, - ranking: _, - axioms: _, - } - | SemanticDeclaration::InferenceRule { - name: _, - type_parameters: _, - logic: _, - binding: _, - guard: _, - conclusion: _, - soundness: _, - progress: _, - executable: _, - axioms: _, - } - | SemanticDeclaration::Verifier { - name: _, - type_parameters: _, - subject: _, - candidate: _, - specification: _, - check: _, - sound: _, - complete: _, - axioms: _, - } - | SemanticDeclaration::Reasoner { - name: _, - type_parameters: _, - logic: _, - observation: _, - observe: _, - rules: _, - strategy: _, - answer: _, - verifier: _, - claims: _, - executable: _, - axioms: _, - }, - ) - | None => return Err(format!("`{module}.{name}` has no termination evidence")), - }; - let plan = - crate::ir::semantic::well_founded_plan(name, group, type_parameters, parameters, body)?; - let site = Site { - module: module.to_owned(), - substitution: BTreeMap::new(), - }; - let mut text = "decreasing_by all_goals first".to_owned(); - for (call, evidence) in plan.sites.iter().zip(&termination.evidence) { - let mut application = - format!(" | (have __evidence := {}", self.member(evidence, &site)?); - for argument in type_arguments { - application.push_str(&format!(" {}", self.ty(argument)?)); - } - for parameter in parameters { - application.push_str(&format!(" ({})", identifier(¶meter.name))); - } - for _ in plan.binders(call) { - application.push_str(" _"); - } - for step in &call.path { - application.push_str(&format!(" ({})", hypothesis(step.node()))); + } + | SemanticDeclaration::InferenceRule { + name: _, + type_parameters: _, + logic: _, + binding: _, + guard: _, + conclusion: _, + soundness: _, + progress: _, + executable: _, + axioms: _, + } + | SemanticDeclaration::Verifier { + name: _, + type_parameters: _, + subject: _, + candidate: _, + specification: _, + check: _, + sound: _, + complete: _, + axioms: _, + } + | SemanticDeclaration::Reasoner { + name: _, + type_parameters: _, + logic: _, + observation: _, + observe: _, + rules: _, + strategy: _, + answer: _, + verifier: _, + claims: _, + executable: _, + axioms: _, + } => {} } - application.push_str("; subst_vars; exact __evidence)"); - text.push_str(&application); - } - text.push('\n'); - Ok(text) - } -} - -impl<'a> Gen<'a> { - fn module( - &mut self, - root_module: &str, - root_name: &str, - report: &RootReport, - name: &str, - ) -> Result { - let graph = self.collect()?; - self.module = name.to_owned(); - let mut out = String::new(); - for module in LIBRARY_MODULES { - out.push_str(&format!("import {module}\n")); - } - let mut imports: Vec<&str> = self - .source - .modules - .values() - .map(|linked| linked.lean_module) - .collect(); - imports.sort_unstable(); - for module in imports { - out.push_str(&format!("import {module}\n")); - } - out.push_str("set_option autoImplicit false\n"); - out.push_str("set_option maxRecDepth 100000\n"); - out.push_str(&super::lower::budget_options(self.program)); - out.push_str("set_option linter.unusedVariables false\n"); - out.push_str(&format!("namespace {name}\n\n")); - out.push_str(&format!( - "def __prog : {SYNTAX}.Program :=\n {}\n\n", - render_program(self.program)? - )); - out.push_str(&self.encoders()?); - self.within(out.len())?; - self.found.clear(); - for component in components(&graph) { - out.push_str(&self.component(&component, &graph)?); - self.within(out.len())?; - } - // The root: function 0, run on its encoded arguments. - let root_lean = self.source.lean_name( - root_module, - &MemberRef { - module: Some(root_module.to_owned()), - name: root_name.to_owned(), - }, - ); - if root_lean != report.root { - return Err(format!( - "the certificate root `{root_lean}` is not the report's `{}`", - report.root - )); - } - let (parameters, result) = self.source.signature(root_module, root_name, &[])?; - let locals: Vec<(Local, SemanticType)> = parameters - .into_iter() - .map(|SemanticParameter { name, r#type }| { - ( - Local { - name, - ty: r#type.clone(), - }, - r#type, - ) - }) - .collect(); - let scope = Self::scope(&locals, ""); - let mut denotation = identifier(&root_lean); - for (local, _) in &locals { - denotation.push_str(&format!(" {}", identifier(&local.name))); - } - let signature = self.signature(0, &scope, &result, &denotation)?; - // The theorem's observation as a definition, so the differential - // evaluator can compute exactly the right-hand side the theorem - // states and compare it with the interpreter's outcome. It is `Rel` - // unfolded to the lazy `cond`, definitionally the same: the value is - // computed only when the width predicate holds, as the interpreter - // computes it, so an input on which the program overflows is not an - // evaluation of the source's own, possibly unbounded, value. - out.push_str(&format!( - "def denote {} : {} :=\n _root_.cond ({}) ({} ({})) {}\n\n", - signature.fits_binders.join(" "), - lib("Obs"), - signature.fits_applied, - lib("Obs.value"), - signature.value, - lib("Obs.overflow") - )); - out.push_str(&format!( - "theorem root {} : {} __prog 0 {} ({} ({}) {}) :=\n {} (__rel_0 {})\n\n", - signature.rel_binders.join(" "), - lib("RunConv"), - signature.arguments, - lib("Rel"), - signature.fits_applied, - signature.value, - lib("run_of_funRel"), - signature.names.join(" ") - )); - let parameter_types: Vec = locals.iter().map(|(_, ty)| ty.clone()).collect(); - out.push_str(&self.boundary(&signature, &signature.names, ¶meter_types)?); - self.within(out.len())?; - out.push_str(&format!("end {name}\n")); - Ok(out) - } - - /// The denotation of a definition instance applied to `locals`. - fn denotation( - &self, - module: &str, - name: &str, - type_arguments: &[SemanticType], - locals: &[CLocal], - ) -> Result { - let mut out = identifier(&self.source.lean_name( - module, - &MemberRef { - module: Some(module.to_owned()), - name: name.to_owned(), - }, - )); - for argument in type_arguments { - out.push_str(&format!(" {}", self.ty(argument)?)); - } - for local in locals { - out.push_str(&format!(" {}", identifier(&local.local.name))); } - Ok(out) + group } - /// One strongly connected component of the relation graph: a single - /// function, or a recursive group emitted as mutual blocks. + /// Record every function the certificate relates and what each one's + /// relation depends on. #[allow(clippy::too_many_lines)] - fn component( - &mut self, - component: &[u64], - graph: &BTreeMap>, - ) -> Result { - let recursive = component.len() > 1 - || graph - .get(&component[0]) - .is_some_and(|edges| edges.contains(&component[0])); - let mut fits_items = Vec::new(); - let mut rel_items = Vec::new(); - for index in component { - let subject = self - .subjects - .get(index) - .cloned() - .ok_or_else(|| format!("function {index} has no subject"))?; - let (fits, rel) = match subject { - Subject::Definition { + fn collect(&mut self) -> Result>, String> { + let mut graph: BTreeMap> = BTreeMap::new(); + let origins: Vec<(u64, Origin)> = self + .layout + .functions + .iter() + .enumerate() + .map(|(index, origin)| (index as u64, origin.clone())) + .collect(); + let mut counters: BTreeMap = BTreeMap::new(); + for (index, origin) in origins { + match origin { + Origin::Definition { module, name, type_arguments, - locals, - result, - body, - recursion, - site, - type_parameters, + instance: _, } => { - let scope = Self::scope(&locals, ""); - let denotation = self.denotation(&module, &name, &type_arguments, &scope)?; - let signature = self.signature(*index, &scope, &result, &denotation)?; - let statement = self.statement(*index, &signature); - match recursion { - Recursion::None => { - if recursive { - return Err(format!( - "`{module}.{name}` recurses without recursion evidence" - )); + let declaration = self.source.declaration(&module, &name); + let ( + type_parameters, + parameters, + body, + recursive_argument, + mutual, + termination, + ) = match declaration { + Some(SemanticDeclaration::Definition { + name: _, + type_parameters, + parameters, + result: _, + recursive_argument, + body, + axioms: _, + executable: _, + mutual, + termination, + production: _, + }) => ( + type_parameters, + parameters, + body, + recursive_argument, + mutual, + termination, + ), + Some( + SemanticDeclaration::Structure { + name: _, + type_parameters: _, + parameters: _, + fields: _, } - let own = self.member( - &MemberRef { - module: Some(module.clone()), - name: name.clone(), - }, - &site, - )?; - let sealed = self.sealed(body, &site, &own)?; - let ctx = - Ctx::new(site, type_parameters, scope, Hypotheses::new(), *index); - let body = self.prove(body, &ctx)?; - ( - format!( - "def __fits_{index} {} : Bool :=\n {}\n\n", - signature.fits_binders.join(" "), - body.fits - ), - format!( - "{sealed}theorem __rel_{index} {} : {statement} :=\n {} rfl rfl {}\n\n", - signature.rel_binders.join(" "), - lib("funRel_intro"), - body.proof - ), - ) - } - Recursion::Structural { argument, mutual } => { - let (scrutinee, branches) = match body { - SemanticTerm::Match { - scrutinee, - branches, - } => (scrutinee, branches), - SemanticTerm::Var { name: _ } - | SemanticTerm::Nat { value: _ } - | SemanticTerm::Integer { - representation: _, - value: _, - } - | SemanticTerm::String { value: _ } - | SemanticTerm::Bytes { hex: _ } - | SemanticTerm::Primitive { - operation: _, - arguments: _, - result: _, - } - | SemanticTerm::Bool { value: _ } - | SemanticTerm::Unit - | SemanticTerm::Nil { element: _ } - | SemanticTerm::Cons { head: _, tail: _ } - | SemanticTerm::Record { - r#type: _, - type_arguments: _, - fields: _, - } - | SemanticTerm::Constructor { - constructor: _, - type_arguments: _, - arguments: _, - } - | SemanticTerm::InstanceValue { - class: _, - arguments: _, - resolved: _, - } - | SemanticTerm::Project { value: _, field: _ } - | SemanticTerm::Call { - function: _, - type_arguments: _, - arguments: _, - } - | SemanticTerm::If { - condition: _, - then_value: _, - else_value: _, - } - | SemanticTerm::Eq { left: _, right: _ } - | SemanticTerm::Le { left: _, right: _ } - | SemanticTerm::Lt { left: _, right: _ } - | SemanticTerm::Add { left: _, right: _ } - | SemanticTerm::Beq { left: _, right: _ } - | SemanticTerm::Ble { left: _, right: _ } - | SemanticTerm::Blt { left: _, right: _ } - | SemanticTerm::And { left: _, right: _ } - | SemanticTerm::PropAnd { left: _, right: _ } - | SemanticTerm::Or { left: _, right: _ } - | SemanticTerm::Not { value: _ } - | SemanticTerm::Implies { - premise: _, - conclusion: _, - } - | SemanticTerm::Iff { left: _, right: _ } - | SemanticTerm::Forall { binder: _, body: _ } - | SemanticTerm::Let { - binder: _, - value: _, - body: _, - } - | SemanticTerm::Pair { left: _, right: _ } - | SemanticTerm::First { value: _ } - | SemanticTerm::Second { value: _ } - | SemanticTerm::Lambda { - parameters: _, - captures: _, - body: _, - } - | SemanticTerm::Apply { - function: _, - arguments: _, - } - | SemanticTerm::FunctionRef { - function: _, - type_arguments: _, - } - | SemanticTerm::MapLiteral { - key: _, - value: _, - entries: _, - } - | SemanticTerm::SetLiteral { - element: _, - elements: _, - } - | SemanticTerm::GraphLiteral { - node: _, - nodes: _, - edges: _, - } - | SemanticTerm::CheckedApply { - model: _, - type_arguments: _, - arguments: _, - checks: _, - } => { - return Err(format!( - "structural `{module}.{name}` is not a top-level match" - )); - } - }; - let scrutinee_is_argument = match scrutinee.as_ref() { - SemanticTerm::Var { name } => name == &argument, - SemanticTerm::Nat { value: _ } - | SemanticTerm::Integer { - representation: _, - value: _, - } - | SemanticTerm::String { value: _ } - | SemanticTerm::Bytes { hex: _ } - | SemanticTerm::Primitive { - operation: _, - arguments: _, - result: _, - } - | SemanticTerm::Bool { value: _ } - | SemanticTerm::Unit - | SemanticTerm::Nil { element: _ } - | SemanticTerm::Cons { head: _, tail: _ } - | SemanticTerm::Record { - r#type: _, - type_arguments: _, - fields: _, - } - | SemanticTerm::Constructor { - constructor: _, - type_arguments: _, - arguments: _, - } - | SemanticTerm::InstanceValue { - class: _, - arguments: _, - resolved: _, - } - | SemanticTerm::Project { value: _, field: _ } - | SemanticTerm::Call { - function: _, - type_arguments: _, - arguments: _, - } - | SemanticTerm::If { - condition: _, - then_value: _, - else_value: _, - } - | SemanticTerm::Match { - scrutinee: _, - branches: _, - } - | SemanticTerm::Eq { left: _, right: _ } - | SemanticTerm::Le { left: _, right: _ } - | SemanticTerm::Lt { left: _, right: _ } - | SemanticTerm::Add { left: _, right: _ } - | SemanticTerm::Beq { left: _, right: _ } - | SemanticTerm::Ble { left: _, right: _ } - | SemanticTerm::Blt { left: _, right: _ } - | SemanticTerm::And { left: _, right: _ } - | SemanticTerm::PropAnd { left: _, right: _ } - | SemanticTerm::Or { left: _, right: _ } - | SemanticTerm::Not { value: _ } - | SemanticTerm::Implies { - premise: _, - conclusion: _, - } - | SemanticTerm::Iff { left: _, right: _ } - | SemanticTerm::Forall { binder: _, body: _ } - | SemanticTerm::Let { - binder: _, - value: _, - body: _, - } - | SemanticTerm::Pair { left: _, right: _ } - | SemanticTerm::First { value: _ } - | SemanticTerm::Second { value: _ } - | SemanticTerm::Lambda { - parameters: _, - captures: _, - body: _, - } - | SemanticTerm::Apply { - function: _, - arguments: _, - } - | SemanticTerm::FunctionRef { - function: _, - type_arguments: _, - } - | SemanticTerm::MapLiteral { - key: _, - value: _, - entries: _, - } - | SemanticTerm::SetLiteral { - element: _, - elements: _, - } - | SemanticTerm::GraphLiteral { - node: _, - nodes: _, - edges: _, - } - | SemanticTerm::CheckedApply { - model: _, - type_arguments: _, - arguments: _, - checks: _, - } => false, - }; - if !scrutinee_is_argument { - return Err(format!( - "structural `{module}.{name}` does not match on `{argument}`" - )); + | SemanticDeclaration::Class { + name: _, + type_parameters: _, + parameters: _, + fields: _, + } + | SemanticDeclaration::Instance { + name: _, + class: _, + arguments: _, + priority: _, + fields: _, + } + | SemanticDeclaration::Inductive { + name: _, + type_parameters: _, + parameters: _, + constructors: _, + mutual: _, + } + | SemanticDeclaration::Theorem { + name: _, + type_parameters: _, + parameters: _, + statement: _, + proof: _, + axioms: _, + } + | SemanticDeclaration::Artifact { + name: _, + role: _, + sha256: _, + length: _, + schema: _, + r#type: _, + axioms: _, + } + | SemanticDeclaration::Contract { + name: _, + type_parameters: _, + input: _, + output: _, + state: _, + precondition: _, + postcondition: _, + invariant: _, + validators: _, + axioms: _, + } + | SemanticDeclaration::Realization { + name: _, + type_parameters: _, + input: _, + output: _, + state: _, + descriptor: _, + executable: _, + axioms: _, + } + | SemanticDeclaration::Evidence { + name: _, + type_parameters: _, + contract: _, + realization: _, + claims: _, + axioms: _, + } + | SemanticDeclaration::Model { + name: _, + type_parameters: _, + contract: _, + realization: _, + evidence: _, + entry: _, + axioms: _, + } + | SemanticDeclaration::Logic { + name: _, + type_parameters: _, + state: _, + relation: _, + invariant: _, + ranking: _, + axioms: _, + } + | SemanticDeclaration::InferenceRule { + name: _, + type_parameters: _, + logic: _, + binding: _, + guard: _, + conclusion: _, + soundness: _, + progress: _, + executable: _, + axioms: _, } - let position = locals - .iter() - .position(|(local, _)| local.name == argument) - .ok_or_else(|| format!("`{argument}` is not a parameter"))?; - let argument_ty = locals[position].0.ty.clone(); - let argument_generic = locals[position].1.clone(); - let generic_site = Site { - module: site.module.clone(), - substitution: BTreeMap::new(), - }; - let mut fits_equations = String::new(); - let mut rel_equations = String::new(); - for (branch_index, branch) in branches.iter().enumerate() { - let (_, fields) = - self.source.branch(branch, &argument_ty, &site)?; - let (_, generic_fields) = - self.source - .branch(branch, &argument_generic, &generic_site)?; - let mut ctx_locals: Vec = scope - .iter() - .filter(|local| local.local.name != argument) - .cloned() - .collect(); - ctx_locals.extend( - branch - .binders - .iter() - .zip(fields.into_iter().zip(generic_fields)) - .map(|(name, (ty, generic))| CLocal { - local: Local { - name: name.clone(), - ty, - }, - generic, - companion: None, - }), - ); - let ctx = Ctx::new( - site.clone(), - type_parameters.clone(), - ctx_locals, - Hypotheses::new(), - *index, - ); - let proof = self.prove(&branch.body, &ctx)?; - let pattern = format!("({})", self.pattern(branch, &site)?); - let patterns: Vec = signature - .names - .iter() - .map(|binder| { - if binder == &identifier(&argument) { - pattern.clone() - } else { - binder.clone() - } - }) - .collect(); - let fits_patterns: Vec = patterns - .iter() - .zip(&signature.names) - .filter(|(_, binder)| { - !binder.starts_with("__fi_") - && !binder.starts_with("__fc_") - && !binder.starts_with("__fh_") - }) - .map(|(pattern, _)| pattern.clone()) - .collect(); - fits_equations.push_str(&format!( - " | {} => (Bool.true && {})\n", - fits_patterns.join(", "), - proof.fits - )); - let mut arm = - format!("({} rfl rfl {})", lib("convA_hit"), proof.proof); - for _ in 0..branch_index { - arm = format!("({} rfl {arm})", lib("convA_miss")); - } - // The arm rewrites by the fits equation - // rather than leaving the unifier to unfold - // `__fits`: through a nested inductive Lean - // has no smart unfolding, and full unfolding - // also reduces the leading `true &&`, which - // misaligns the conjunction. - rel_equations.push_str(&format!( - " | {} => by rw [__fits_{index}.eq_def]; exact {} rfl rfl ({} ({} rfl) {arm})\n", - patterns.join(", "), - lib("funRel_intro"), - lib("conv_match"), - lib("conv_var") - )); + | SemanticDeclaration::Verifier { + name: _, + type_parameters: _, + subject: _, + candidate: _, + specification: _, + check: _, + sound: _, + complete: _, + axioms: _, } - let structural = |names: &[String]| match &mutual { - Some(_) => format!( - "termination_by structural {} => {}\n", - names - .iter() - .map(|binder| { - if binder == &identifier(&argument) { - binder.clone() - } else { - "_".to_owned() - } - }) - .collect::>() - .join(" "), - identifier(&argument) - ), - None => String::new(), - }; - let fits_names: Vec = signature - .names - .iter() - .filter(|binder| { - !binder.starts_with("__fi_") - && !binder.starts_with("__fc_") - && !binder.starts_with("__fh_") - }) - .cloned() - .collect(); + | SemanticDeclaration::Reasoner { + name: _, + type_parameters: _, + logic: _, + observation: _, + observe: _, + rules: _, + strategy: _, + answer: _, + verifier: _, + claims: _, + executable: _, + axioms: _, + }, + ) + | None => return Err(format!("`{module}.{name}` is not a definition")), + }; + let site = Site { + module: module.clone(), + substitution: Source::instantiation(type_parameters, &type_arguments)?, + }; + let (closed, result) = + self.source.signature(&module, &name, &type_arguments)?; + let identity: Vec = type_parameters + .iter() + .map(|parameter| SemanticType::Parameter { + name: parameter.clone(), + }) + .collect(); + let (generic, _) = self.source.signature(&module, &name, &identity)?; + let locals: Vec<(Local, SemanticType)> = closed + .into_iter() + .zip(generic) + .map(|(closed, generic)| { ( - format!( - "def __fits_{index} : ∀ {}, Bool\n{fits_equations}{}\n", - signature.fits_binders.join(" "), - structural(&fits_names) - ), - format!( - "theorem __rel_{index} : ∀ {}, {statement}\n{rel_equations}{}\n", - signature.rel_binders.join(" "), - structural(&signature.names) - ), + Local { + name: closed.name, + ty: closed.r#type, + }, + generic.r#type, ) - } - Recursion::WellFounded { termination, group } => { - let declaration = self.source.declaration(&module, &name); - let (declared_type_parameters, source_parameters) = match declaration { - Some(SemanticDeclaration::Definition { - name: _, - type_parameters, - parameters, - result: _, - recursive_argument: _, - body: _, - axioms: _, - executable: _, - mutual: _, - termination: _, - production: _, - }) => (type_parameters, parameters), - Some( - SemanticDeclaration::Structure { - name: _, - type_parameters: _, - parameters: _, - fields: _, - } - | SemanticDeclaration::Class { - name: _, - type_parameters: _, - parameters: _, - fields: _, - } - | SemanticDeclaration::Instance { - name: _, - class: _, - arguments: _, - priority: _, - fields: _, - } - | SemanticDeclaration::Inductive { - name: _, - type_parameters: _, - parameters: _, - constructors: _, - mutual: _, - } - | SemanticDeclaration::Theorem { - name: _, - type_parameters: _, - parameters: _, - statement: _, - proof: _, - axioms: _, - } - | SemanticDeclaration::Artifact { - name: _, - role: _, - sha256: _, - length: _, - schema: _, - r#type: _, - axioms: _, - } - | SemanticDeclaration::Contract { - name: _, - type_parameters: _, - input: _, - output: _, - state: _, - precondition: _, - postcondition: _, - invariant: _, - validators: _, - axioms: _, - } - | SemanticDeclaration::Realization { - name: _, - type_parameters: _, - input: _, - output: _, - state: _, - descriptor: _, - executable: _, - axioms: _, - } - | SemanticDeclaration::Evidence { - name: _, - type_parameters: _, - contract: _, - realization: _, - claims: _, - axioms: _, - } - | SemanticDeclaration::Model { - name: _, - type_parameters: _, - contract: _, - realization: _, - evidence: _, - entry: _, - axioms: _, - } - | SemanticDeclaration::Logic { - name: _, - type_parameters: _, - state: _, - relation: _, - invariant: _, - ranking: _, - axioms: _, - } - | SemanticDeclaration::InferenceRule { - name: _, - type_parameters: _, - logic: _, - binding: _, - guard: _, - conclusion: _, - soundness: _, - progress: _, - executable: _, - axioms: _, - } - | SemanticDeclaration::Verifier { - name: _, - type_parameters: _, - subject: _, - candidate: _, - specification: _, - check: _, - sound: _, - complete: _, - axioms: _, - } - | SemanticDeclaration::Reasoner { - name: _, - type_parameters: _, - logic: _, - observation: _, - observe: _, - rules: _, - strategy: _, - answer: _, - verifier: _, - claims: _, - executable: _, - axioms: _, - }, - ) - | None => { - return Err(format!("`{module}.{name}` is not a definition")) - } - }; - let hypotheses = Self::hypotheses( - &name, - &group, - declared_type_parameters, - source_parameters, - body, - )?; - let ctx = Ctx::new( - site.clone(), - type_parameters.clone(), - scope.clone(), - hypotheses, - *index, - ); - let proof = self.prove(body, &ctx)?; - let measure_ctx = Ctx::new( - site.clone(), - type_parameters.clone(), - scope.clone(), - Hypotheses::new(), - *index, - ); - let measure = self.src(&termination.measure, &measure_ctx)?; - let decreasing = - self.decreasing(&module, &name, &type_arguments, &group)?; + }) + .collect(); + let instance_parameters: Vec<(String, SemanticType)> = type_parameters + .iter() + .cloned() + .zip(type_arguments.iter().cloned()) + .collect(); + let (recursion, hypotheses) = match termination { + Some(termination) => { + let group = self.group(&module, &name, mutual.as_ref()); + let hypotheses = + Self::hypotheses(&name, &group, type_parameters, parameters, body)?; ( - format!( - "def __fits_{index} {} : Bool :=\n {}\ntermination_by {measure}\n{decreasing}\n", - signature.fits_binders.join(" "), - proof.fits - ), - format!( - "theorem __rel_{index} {} : {statement} := by\n rw [{}.eq_def, __fits_{index}.eq_def]\n exact {} rfl rfl {}\ntermination_by {measure}\n{decreasing}\n", - signature.rel_binders.join(" "), - identifier(&self.source.lean_name(&module, &MemberRef { - module: Some(module.clone()), - name: name.clone(), - })), - lib("funRel_intro"), - proof.proof - ), + Recursion::WellFounded { + termination: termination.clone(), + group, + }, + hypotheses, ) } - } - } - Subject::Instance { - module, - name, - class, - fields, - } => { - if recursive { - return Err(format!("instance `{module}.{name}` is recursive")); - } + None => match recursive_argument { + Some(argument) => ( + Recursion::Structural { + argument: argument.clone(), + mutual: mutual.clone(), + }, + Hypotheses::new(), + ), + None => (Recursion::None, Hypotheses::new()), + }, + }; let ctx = Ctx::new( - Site { - module: module.clone(), - substitution: BTreeMap::new(), + site.clone(), + instance_parameters.clone(), + Self::scope(&locals, ""), + hypotheses, + index, + ); + let mut dependencies = BTreeSet::new(); + self.discover(body, &ctx, &mut dependencies, &mut counters)?; + graph.insert(index, dependencies); + self.subjects.insert( + index, + Subject::Definition { + module, + name, + type_arguments, + locals, + result, + body, + recursion, + site, + type_parameters: instance_parameters, + }, + ); + } + Origin::Instance { module, name } => { + let (class, arguments, fields) = match self.source.declaration(&module, &name) { + Some(SemanticDeclaration::Instance { + name: _, + class, + arguments, + priority: _, + fields, + }) => (class, arguments, fields), + Some( + SemanticDeclaration::Structure { + name: _, + type_parameters: _, + parameters: _, + fields: _, + } + | SemanticDeclaration::Class { + name: _, + type_parameters: _, + parameters: _, + fields: _, + } + | SemanticDeclaration::Definition { + name: _, + type_parameters: _, + parameters: _, + result: _, + recursive_argument: _, + body: _, + axioms: _, + executable: _, + mutual: _, + termination: _, + production: _, + } + | SemanticDeclaration::Inductive { + name: _, + type_parameters: _, + parameters: _, + constructors: _, + mutual: _, + } + | SemanticDeclaration::Theorem { + name: _, + type_parameters: _, + parameters: _, + statement: _, + proof: _, + axioms: _, + } + | SemanticDeclaration::Artifact { + name: _, + role: _, + sha256: _, + length: _, + schema: _, + r#type: _, + axioms: _, + } + | SemanticDeclaration::Contract { + name: _, + type_parameters: _, + input: _, + output: _, + state: _, + precondition: _, + postcondition: _, + invariant: _, + validators: _, + axioms: _, + } + | SemanticDeclaration::Realization { + name: _, + type_parameters: _, + input: _, + output: _, + state: _, + descriptor: _, + executable: _, + axioms: _, + } + | SemanticDeclaration::Evidence { + name: _, + type_parameters: _, + contract: _, + realization: _, + claims: _, + axioms: _, + } + | SemanticDeclaration::Model { + name: _, + type_parameters: _, + contract: _, + realization: _, + evidence: _, + entry: _, + axioms: _, + } + | SemanticDeclaration::Logic { + name: _, + type_parameters: _, + state: _, + relation: _, + invariant: _, + ranking: _, + axioms: _, + } + | SemanticDeclaration::InferenceRule { + name: _, + type_parameters: _, + logic: _, + binding: _, + guard: _, + conclusion: _, + soundness: _, + progress: _, + executable: _, + axioms: _, + } + | SemanticDeclaration::Verifier { + name: _, + type_parameters: _, + subject: _, + candidate: _, + specification: _, + check: _, + sound: _, + complete: _, + axioms: _, + } + | SemanticDeclaration::Reasoner { + name: _, + type_parameters: _, + logic: _, + observation: _, + observe: _, + rules: _, + strategy: _, + answer: _, + verifier: _, + claims: _, + executable: _, + axioms: _, + }, + ) + | None => return Err(format!("`{module}.{name}` is not an instance")), + }; + let site = Site { + module: module.clone(), + substitution: BTreeMap::new(), + }; + let class = self.source.close( + &SemanticType::Named { + member: class.clone(), + arguments: arguments.clone(), }, - Vec::new(), - Vec::new(), - Hypotheses::new(), - *index, + &site, ); - let record = self.record(&class, fields, &ctx)?; - let instance = identifier(&self.source.lean_name( - &module, - &MemberRef { - module: Some(module.clone()), - name: name.clone(), + let ctx = Ctx::new(site, Vec::new(), Vec::new(), Hypotheses::new(), index); + let mut dependencies = BTreeSet::new(); + for SemanticAssignment { field: _, value } in fields { + self.discover(value, &ctx, &mut dependencies, &mut counters)?; + } + graph.insert(index, dependencies); + self.subjects.insert( + index, + Subject::Instance { + module, + name, + class, + fields, }, - )); - ( - format!("def __fits_{index} : Bool :=\n {}\n\n", record.fits), - format!( - "theorem __rel_{index} : {} __prog {index} [] ({} __fits_{index} ({} {instance})) :=\n {} rfl rfl {}\n\n", - lib("FunRel"), - lib("Rel"), - self.enc(&class)?, - lib("funRel_intro"), - record.proof - ), - ) + ); + } + Origin::Lambda { + owner: _, + ordinal: _, + } + | Origin::Template { + template: _, + types: _, + entry: _, + member: _, + } + | Origin::Validator { + ty: _, + module: _, + kind: _, } + | Origin::Entry { validators: _ } => {} + } + } + // Lambdas were registered as their owners were walked; walk each + // lambda body for its own dependencies and nested lambdas. + let mut pending: Vec = self + .subjects + .iter() + .filter_map(|(index, subject)| match subject { Subject::Lambda { site: _, captures: _, parameters: _, - body, + body: _, hypotheses: _, type_parameters: _, - } => { - if recursive { - return Err(format!("lambda function {index} is recursive")); - } - let ctx = self.lambda_ctx(*index)?; - let result = self.source.infer(body, &ctx.sources(), &ctx.site)?; - let denotation = self.src(body, &ctx)?; - let signature = self.signature(*index, &ctx.locals, &result, &denotation)?; - let statement = self.statement(*index, &signature); - let sealed = self.sealed(body, &ctx.site, "")?; - let proof = self.prove(body, &ctx)?; - ( - format!( - "def __fits_{index} {} : Bool :=\n {}\n\n", - signature.fits_binders.join(" "), - proof.fits - ), - format!( - "{sealed}theorem __rel_{index} {} : {statement} :=\n {} rfl rfl {}\n\n", - signature.rel_binders.join(" "), - lib("funRel_intro"), - proof.proof - ), - ) + } => Some(*index), + Subject::Definition { + module: _, + name: _, + type_arguments: _, + locals: _, + result: _, + body: _, + recursion: _, + site: _, + type_parameters: _, } + | Subject::Instance { + module: _, + name: _, + class: _, + fields: _, + } => None, + }) + .collect(); + loop { + let index = match pending.pop() { + Some(index) => index, + None => break, }; - fits_items.push(fits); - rel_items.push(rel); - } - Ok(format!( - "{}{}", - mutual_block(&fits_items), - mutual_block(&rel_items) - )) - } -} - -/// A clause proof that unfolds a collection-runtime function once and -/// rewrites by the comparison hypothesis `h`, with `binders` wildcards -/// before it. -fn clause(binders: usize, function: &str) -> String { - format!( - "(fun{} __h => by simp only [{function}, __h])", - " _".repeat(binders) - ) -} - -/// A clause of the pair key order: the order's instances unfold in the -/// hypothesis and the goal alike before the hypothesis rewrites. -fn pair_clause(compare: &str) -> String { - format!("(fun _ _ __h => by simp only [{compare}] at __h ⊢; simp only [__h])") -} - -/// The Lean type of an integer literal's representation. -const fn integer_lean(representation: SemanticInteger) -> &'static str { - match representation { - SemanticInteger::Int => "Int", - SemanticInteger::Int8 => "Int8", - SemanticInteger::Int16 => "Int16", - SemanticInteger::Int32 => "Int32", - SemanticInteger::Int64 => "Int64", - SemanticInteger::UInt8 => "UInt8", - SemanticInteger::UInt16 => "UInt16", - SemanticInteger::UInt32 => "UInt32", - SemanticInteger::UInt64 => "UInt64", - } -} - -/// A clause that holds by computation, over `binders` arguments. -fn computed(binders: usize) -> String { - if binders == 0 { - "rfl".to_owned() - } else { - format!("(fun{} => rfl)", " _".repeat(binders)) - } -} - -/// The key type, value type, and node type of a template instance. -fn instance_type(instance: &[SemanticType], position: usize) -> Result { - instance - .get(position) - .cloned() - .ok_or_else(|| format!("a template instance lacks type argument {position}")) -} - -impl Gen<'_> { - /// The target type of a closed source type, with document types read - /// from the layout. - fn target_ty(&self, ty: &SemanticType) -> Result { - Ok(match ty { - SemanticType::Nat => Ty::Nat, - SemanticType::Bool => Ty::Bool, - SemanticType::ContractViolation | SemanticType::ReasoningFailure => Ty::Pair { - left: Box::new(Ty::Bool), - right: Box::new(Ty::Bool), - }, - SemanticType::Unit => Ty::Unit, - SemanticType::Int => Ty::Int, - SemanticType::Int8 - | SemanticType::Int16 - | SemanticType::Int32 - | SemanticType::Int64 - | SemanticType::UInt8 - | SemanticType::UInt16 - | SemanticType::UInt32 - | SemanticType::UInt64 => Ty::Fixed { - width: fixed_kind(ty).ok_or("a fixed width without a kind")?, - }, - SemanticType::String => Ty::String, - SemanticType::Bytes => Ty::Bytes, - SemanticType::Ordering => Ty::Ordering, - SemanticType::Option { value } => Ty::Option { - value: Box::new(self.target_ty(value)?), - }, - SemanticType::Result { ok, error } => Ty::Result { - ok: Box::new(self.target_ty(ok)?), - error: Box::new(self.target_ty(error)?), - }, - SemanticType::List { element } | SemanticType::Set { element } => Ty::List { - element: Box::new(self.target_ty(element)?), - }, - SemanticType::Product { left, right } => Ty::Pair { - left: Box::new(self.target_ty(left)?), - right: Box::new(self.target_ty(right)?), - }, - SemanticType::Map { key, value } => Ty::List { - element: Box::new(Ty::Pair { - left: Box::new(self.target_ty(key)?), - right: Box::new(self.target_ty(value)?), - }), - }, - SemanticType::Function { parameters, result } => Ty::Fn { - parameters: parameters - .iter() - .map(|parameter| self.target_ty(parameter)) - .collect::, _>>()?, - result: Box::new(self.target_ty(result)?), - }, - SemanticType::Named { - member: _, - arguments: _, - } => { - let text = self.source.type_text(ty); - Ty::Adt { - index: *self - .adts - .get(&text) - .ok_or_else(|| format!("`{text}` has no ADT in the layout"))?, - } - } - SemanticType::Type | SemanticType::Prop | SemanticType::Parameter { name: _ } => { - return Err(format!( - "`{}` has no runtime realization", - self.source.type_text(ty) - )); + if graph.contains_key(&index) { + continue; } - }) - } - - /// The source key order of `ty` in the collection runtime `coll`. - fn key_order(&self, ty: &SemanticType, coll: &str) -> Result { - let lean = self.ty(ty)?; - Ok(format!( - "({coll}.Key.compare : {lean} -> {lean} -> Ordering)" - )) + let ctx = self.lambda_ctx(index)?; + let body = match self.subjects.get(&index) { + Some(Subject::Lambda { + site: _, + captures: _, + parameters: _, + body, + hypotheses: _, + type_parameters: _, + }) => *body, + Some( + Subject::Definition { + module: _, + name: _, + type_arguments: _, + locals: _, + result: _, + body: _, + recursion: _, + site: _, + type_parameters: _, + } + | Subject::Instance { + module: _, + name: _, + class: _, + fields: _, + }, + ) + | None => return Err(format!("function {index} is not a lambda")), + }; + let before: BTreeSet = self.subjects.keys().copied().collect(); + let mut dependencies = BTreeSet::new(); + self.discover(body, &ctx, &mut dependencies, &mut counters)?; + graph.insert(index, dependencies); + pending.extend( + self.subjects + .keys() + .copied() + .filter(|key| !before.contains(key)), + ); + } + Ok(graph) } - /// The proof that the realization's comparison agrees with the key - /// order of `ty` in the collection runtime `coll`. - fn key_spec(&self, ty: &SemanticType, coll: &str) -> Result { - let order = self.key_order(ty, coll)?; - let scalar = |lemma: &str| format!("({} {order} (fun _ _ => rfl))", lib(lemma)); - Ok(match ty { - SemanticType::Nat => scalar("keySpec_nat"), - SemanticType::Int => scalar("keySpec_int"), - SemanticType::Bool => scalar("keySpec_bool"), - SemanticType::Int8 => scalar("keySpec_i8"), - SemanticType::Int16 => scalar("keySpec_i16"), - SemanticType::Int32 => scalar("keySpec_i32"), - SemanticType::Int64 => scalar("keySpec_i64"), - SemanticType::UInt8 => scalar("keySpec_u8"), - SemanticType::UInt16 => scalar("keySpec_u16"), - SemanticType::UInt32 => scalar("keySpec_u32"), - SemanticType::UInt64 => scalar("keySpec_u64"), - SemanticType::String => { - let codes = format!("{coll}.compareCodes"); - format!( - "({} {codes} rfl (fun _ _ => rfl) (fun _ _ => rfl) {} {} {} {order} (fun _ _ => rfl))", - lib("keySpec_string"), - clause(4, &codes), - clause(4, &codes), - clause(4, &codes) - ) - } - SemanticType::Product { left, right } => { - let compare = format!("{coll}.Key.compare"); - format!( - "({} {} {} {order} {} {} {})", - lib("keySpec_pair"), - self.key_spec(left, coll)?, - self.key_spec(right, coll)?, - pair_clause(&compare), - pair_clause(&compare), - pair_clause(&compare) - ) - } - SemanticType::Unit - | SemanticType::Bytes - | SemanticType::Ordering - | SemanticType::Option { value: _ } - | SemanticType::Result { ok: _, error: _ } - | SemanticType::List { element: _ } - | SemanticType::Set { element: _ } - | SemanticType::Map { key: _, value: _ } - | SemanticType::Named { - member: _, - arguments: _, - } - | SemanticType::Function { - parameters: _, - result: _, - } - | SemanticType::Type - | SemanticType::Prop - | SemanticType::Parameter { name: _ } - | SemanticType::ContractViolation - | SemanticType::ReasoningFailure => { - return Err(format!("`{}` has no key order", self.source.type_text(ty))); + /// The scope of a lambda's own function: its captures, then its + /// parameters. + fn lambda_ctx(&self, index: u64) -> Result { + match self.subjects.get(&index) { + Some(Subject::Lambda { + site, + captures, + parameters, + body: _, + hypotheses, + type_parameters, + }) => { + let mut locals = captures.clone(); + locals.extend(Self::scope(parameters, "p")); + Ok(Ctx::new( + site.clone(), + type_parameters.clone(), + locals, + hypotheses.clone(), + index, + )) } - }) - } - - /// The set-runtime clauses of key type `ty`. - fn set_ops(&self, ty: &SemanticType, coll: &str) -> Result { - let insert = format!("{coll}.insertElement"); - let remove = format!("{coll}.removeElement"); - let contains = format!("{coll}.containsElement"); - Ok(format!( - "(⟨{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}⟩ : {} {} {insert} {remove} {contains})", - computed(1), - clause(3, &insert), - clause(3, &insert), - clause(3, &insert), - computed(1), - clause(3, &remove), - clause(3, &remove), - clause(3, &remove), - computed(1), - clause(3, &contains), - clause(3, &contains), - clause(3, &contains), - lib("SetOps"), - self.key_order(ty, coll)? - )) - } - - /// The lookup-runtime clauses of key type `key` and value type `value`. - fn look_ops( - &self, - key: &SemanticType, - value: &SemanticType, - coll: &str, - ) -> Result { - let lookup = format!("{coll}.lookupEntry"); - Ok(format!( - "(⟨{}, {}, {}, {}⟩ : {} {} ({lookup} : {} -> List (Prod {} {}) -> Option {}))", - computed(1), - clause(4, &lookup), - clause(4, &lookup), - clause(4, &lookup), - lib("LookOps"), - self.key_order(key, coll)?, - self.ty(key)?, - self.ty(key)?, - self.ty(value)?, - self.ty(value)? - )) + Some( + Subject::Definition { + module: _, + name: _, + type_arguments: _, + locals: _, + result: _, + body: _, + recursion: _, + site: _, + type_parameters: _, + } + | Subject::Instance { + module: _, + name: _, + class: _, + fields: _, + }, + ) + | None => Err(format!("function {index} is not a lambda")), + } } - /// `n` function-placement hypotheses, each discharged by computation. - fn placed(n: usize) -> String { - vec!["rfl"; n].join(" ") + fn discover_all( + &mut self, + terms: &'a [SemanticTerm], + ctx: &Ctx, + dependencies: &mut BTreeSet, + counters: &mut BTreeMap, + ) -> Result<(), String> { + for term in terms { + self.discover(term, ctx, dependencies, counters)?; + } + Ok(()) } - /// A call of a collection template: the call's operands, then the - /// template's lemma instantiated at the call's runtime functions, whose - /// defining clauses the certificate discharges by unfolding them. + /// Walk a body in lowering order, recording the functions its relation + /// depends on and registering each lambda it forms. #[allow(clippy::too_many_lines)] - fn template_call( + fn discover( &mut self, - template: Template, - instance: &[SemanticType], - arguments: &[SemanticTerm], - types: &[SemanticType], + term: &'a SemanticTerm, ctx: &Ctx, - ) -> Result { - let tys = instance - .iter() - .map(|ty| self.target_ty(ty)) - .collect::, _>>()?; - let entry = *self - .templates - .get(&(template.name().to_owned(), tys.clone())) - .ok_or_else(|| format!("template {} has no instance in the layout", template.name()))?; - let rendered: Vec = tys.iter().map(render_ty).collect(); - let coll = format!("{}.LexLeanCollections", self.lean_module(&ctx.site.module)?); - let mut proofs = Vec::new(); - let mut values = Vec::new(); - let mut functions = Vec::new(); - for argument in arguments { - match self.function_argument(argument, ctx)? { - Some(companion) => { - values.push(format!("({})", companion.value)); - proofs.push(companion.proof.clone()); - functions.push(companion); + dependencies: &mut BTreeSet, + counters: &mut BTreeMap, + ) -> Result<(), String> { + let site = ctx.site.clone(); + match term { + SemanticTerm::Var { name: _ } + | SemanticTerm::Nat { value: _ } + | SemanticTerm::Integer { + representation: _, + value: _, + } + | SemanticTerm::String { value: _ } + | SemanticTerm::Bytes { hex: _ } + | SemanticTerm::Bool { value: _ } + | SemanticTerm::Unit + | SemanticTerm::Nil { element: _ } => Ok(()), + SemanticTerm::Primitive { + operation: _, + arguments, + result: _, + } + | SemanticTerm::Constructor { + constructor: _, + type_arguments: _, + arguments, + } => self.discover_all(arguments, ctx, dependencies, counters), + SemanticTerm::Cons { + head: left, + tail: right, + } + | SemanticTerm::Add { left, right } + | SemanticTerm::Beq { left, right } + | SemanticTerm::Ble { left, right } + | SemanticTerm::Blt { left, right } + | SemanticTerm::And { left, right } + | SemanticTerm::Or { left, right } + | SemanticTerm::Pair { left, right } => { + self.discover(left, ctx, dependencies, counters)?; + self.discover(right, ctx, dependencies, counters) + } + SemanticTerm::Record { + r#type: _, + type_arguments: _, + fields, + } => { + let ty = self.source.infer(term, &ctx.sources(), &site)?; + let shape = self.source.document(&ty)?; + for field in &shape.field_names { + let SemanticAssignment { field: _, value } = fields + .iter() + .find(|assignment| &assignment.field == field) + .ok_or_else(|| format!("field `{field}` is not assigned"))?; + self.discover(value, ctx, dependencies, counters)?; } - None => { - values.push(format!("({})", self.src(argument, ctx)?)); - proofs.push(self.prove(argument, ctx)?); + Ok(()) + } + SemanticTerm::InstanceValue { + class: _, + arguments: _, + resolved, + } => { + let module = Source::module_of(resolved, &site); + dependencies.insert(self.instance_index(&module, &resolved.name, &[])?); + Ok(()) + } + SemanticTerm::Project { value, field: _ } + | SemanticTerm::Not { value } + | SemanticTerm::First { value } + | SemanticTerm::Second { value } => self.discover(value, ctx, dependencies, counters), + SemanticTerm::Call { + function, + type_arguments, + arguments, + } => { + let module = Source::module_of(function, &site); + let closed: Vec = type_arguments + .iter() + .map(|argument| self.source.close(argument, &site)) + .collect(); + dependencies.insert(self.instance_index(&module, &function.name, &closed)?); + self.discover_all(arguments, ctx, dependencies, counters) + } + SemanticTerm::FunctionRef { + function, + type_arguments, + } => { + let module = Source::module_of(function, &site); + let closed: Vec = type_arguments + .iter() + .map(|argument| self.source.close(argument, &site)) + .collect(); + dependencies.insert(self.instance_index(&module, &function.name, &closed)?); + Ok(()) + } + SemanticTerm::If { + condition, + then_value, + else_value, + } => { + self.discover(condition, ctx, dependencies, counters)?; + self.discover(then_value, ctx, dependencies, counters)?; + self.discover(else_value, ctx, dependencies, counters) + } + SemanticTerm::Match { + scrutinee, + branches, + } => { + self.discover(scrutinee, ctx, dependencies, counters)?; + for branch in branches { + let inner = ctx.with(self.branch_locals(branch, scrutinee, ctx)?); + self.discover(&branch.body, &inner, dependencies, counters)?; } + Ok(()) } - } - let value = |index: usize| -> Result { - values - .get(index) - .cloned() - .ok_or_else(|| format!("template {} lacks operand {index}", template.name())) - }; - let argument_type = |index: usize| -> Result { - types - .get(index) - .cloned() - .ok_or_else(|| format!("template {} lacks operand {index}", template.name())) - }; - let step = || -> Result<&FunctionArgument, String> { - functions - .first() - .ok_or_else(|| format!("template {} lacks its step", template.name())) - }; - let at = |names: &[&str]| -> String { - names - .iter() - .zip(&rendered) - .map(|(name, ty)| format!(" ({name} := {ty})")) - .collect::() - }; - let head = format!("(p := __prog) (fi := {entry})"); - let insert_entry = format!("{coll}.insertEntry"); - let remove_entry = format!("{coll}.removeEntry"); - let lookup_entry = format!("{coll}.lookupEntry"); - let (lemma, fits) = match template { - Template::MapInsert => { - let key = instance_type(instance, 0)?; - let stored = instance_type(instance, 1)?; - ( - format!( - "({} {} {} {} (fun _ => rfl) {insert_entry} {} {} {} {} {head}{} rfl {} {} {})", - lib("tpl_mapInsert"), - self.key_spec(&key, &coll)?, - self.enc(&stored)?, - self.list_bundle(&argument_type(0)?)?, - computed(2), - clause(5, &insert_entry), - clause(5, &insert_entry), - clause(5, &insert_entry), - at(&["tk", "tw"]), - value(0)?, - value(1)?, - value(2)? - ), - TRUE.to_owned(), - ) + SemanticTerm::Let { + binder, + value, + body, + } => { + self.discover(value, ctx, dependencies, counters)?; + let inner = ctx.with(vec![self.let_local(binder, ctx)]); + self.discover(body, &inner, dependencies, counters) } - Template::MapRemove => { - let key = instance_type(instance, 0)?; - let stored = instance_type(instance, 1)?; - ( - format!( - "({} {} {} {} (fun _ => rfl) {remove_entry} {} {} {} {} {head}{} rfl {} {})", - lib("tpl_mapRemove"), - self.key_spec(&key, &coll)?, - self.enc(&stored)?, - self.list_bundle(&argument_type(0)?)?, - computed(1), - clause(4, &remove_entry), - clause(4, &remove_entry), - clause(4, &remove_entry), - at(&["tk", "tw"]), - value(0)?, - value(1)? - ), - TRUE.to_owned(), - ) + SemanticTerm::Lambda { + parameters, + captures, + body, + } => { + let ordinal = counters.entry(ctx.function).or_insert(0); + let current = *ordinal; + *ordinal += 1; + let index = self + .lambdas + .get(&(ctx.function, current)) + .copied() + .ok_or_else(|| { + format!( + "lambda {current} of function {} is not in the layout", + ctx.function + ) + })?; + dependencies.insert(index); + let mut captured = Vec::new(); + for capture in captures { + let local = ctx.lookup(capture)?; + captured.push((local.local.clone(), local.generic.clone())); + } + let parameters = parameters + .iter() + .map(|binder| self.let_local(binder, ctx)) + .collect(); + self.subjects.insert( + index, + Subject::Lambda { + site: site.clone(), + captures: Self::scope(&captured, "c"), + parameters, + body, + hypotheses: ctx.hypotheses.clone(), + type_parameters: ctx.parameters.clone(), + }, + ); + Ok(()) } - Template::MapLookup => { - let key = instance_type(instance, 0)?; - let stored = instance_type(instance, 1)?; - ( - format!( - "({} {} {} {} (fun _ => rfl) {} (fun _ => rfl) {lookup_entry} {} {} {} {} {head}{} rfl {} {})", - lib("tpl_mapLookup"), - self.key_spec(&key, &coll)?, - self.enc(&stored)?, - self.list_bundle(&argument_type(0)?)?, - self.option_bundle(&SemanticType::Option { - value: Box::new(stored.clone()), - })?, - computed(1), - clause(4, &lookup_entry), - clause(4, &lookup_entry), - clause(4, &lookup_entry), - at(&["tk", "tw"]), - value(0)?, - value(1)? - ), - TRUE.to_owned(), - ) + SemanticTerm::Apply { + function, + arguments, + } => { + self.discover(function, ctx, dependencies, counters)?; + self.discover_all(arguments, ctx, dependencies, counters) } - Template::MapContains => { - let key = instance_type(instance, 0)?; - let stored = instance_type(instance, 1)?; - ( - format!( - "({} {} {} {} (fun _ => rfl) {lookup_entry} {} {} {} {} {head}{} {} {} {})", - lib("tpl_mapContains"), - self.key_spec(&key, &coll)?, - self.enc(&stored)?, - self.list_bundle(&argument_type(0)?)?, - computed(1), - clause(4, &lookup_entry), - clause(4, &lookup_entry), - clause(4, &lookup_entry), - at(&["tk", "tw"]), - Self::placed(2), - value(0)?, - value(1)? - ), - TRUE.to_owned(), - ) + SemanticTerm::MapLiteral { + key: _, + value: _, + entries, + } => { + for SemanticMapEntry { key, value } in entries { + self.discover(key, ctx, dependencies, counters)?; + self.discover(value, ctx, dependencies, counters)?; + } + Ok(()) } - Template::MapKeys | Template::MapValues => { - let key = instance_type(instance, 0)?; - let stored = instance_type(instance, 1)?; - let (lemma, projected) = match template { - Template::MapKeys => ("tpl_mapKeys", key.clone()), - Template::MapValues => ("tpl_mapValues", stored.clone()), - Template::MapInsert - | Template::MapRemove - | Template::MapLookup - | Template::MapContains - | Template::MapFold - | Template::SetInsert - | Template::SetRemove - | Template::SetContains - | Template::SetUnion - | Template::SetIntersection - | Template::SetDifference - | Template::SetFold - | Template::ListFold - | Template::Iterate - | Template::IterateUntil - | Template::GraphSuccessors - | Template::GraphReachable - | Template::GraphTopological => { - return Err("a projection of a non-projection template".to_owned()); + SemanticTerm::SetLiteral { + element: _, + elements, + } => self.discover_all(elements, ctx, dependencies, counters), + // Certificates read elaborated declarations, in which a checked + // application is the ordinary term it means (§17.12). + SemanticTerm::CheckedApply { + model: _, + type_arguments: _, + arguments: _, + checks: _, + } => Err( + "a checked model application is certified only through its elaboration".to_owned(), + ), + SemanticTerm::GraphLiteral { + node: _, + nodes, + edges, + } => { + for source in nodes { + for SemanticEdge { + source: from, + target, + } in edges + { + if from == source { + self.discover(target, ctx, dependencies, counters)?; + } } - }; - ( - format!( - "({} (ek := {}) (ev := {}) {} (fun _ => rfl) {} (fun _ => rfl) {head}{} rfl {})", - lib(lemma), - self.enc(&key)?, - self.enc(&stored)?, - self.list_bundle(&argument_type(0)?)?, - self.list_bundle(&SemanticType::List { - element: Box::new(projected), - })?, - at(&["tk", "tw"]), - value(0)? - ), - TRUE.to_owned(), - ) + self.discover(source, ctx, dependencies, counters)?; + } + Ok(()) } - Template::MapFold => { - let key = instance_type(instance, 0)?; - let stored = instance_type(instance, 1)?; - let state = instance_type(instance, 2)?; - let step = step()?; - ( - format!( - "({} (ek := {}) (ev := {}) (es := {}) {} (fun _ => rfl) {} ({}) {} {} ({}) {head}{} rfl {} {})", - lib("tpl_mapFold"), - self.enc(&key)?, - self.enc(&stored)?, - self.enc(&state)?, - self.list_bundle(&argument_type(2)?)?, - value(0)?, - step.fits, - step.index, - step.captures, - step.relation, - at(&["tk", "tw", "ts"]), - value(1)?, - value(2)? - ), - format!( - "({} (fun __st __e => ({}) __st __e.1 __e.2) (fun __st __e => {} __st __e.1 __e.2) {} {})", - lib("foldFits"), - step.fits, - value(0)?, - value(1)?, - value(2)? - ), - ) + SemanticTerm::Eq { left: _, right: _ } + | SemanticTerm::Le { left: _, right: _ } + | SemanticTerm::Lt { left: _, right: _ } + | SemanticTerm::PropAnd { left: _, right: _ } + | SemanticTerm::Implies { + premise: _, + conclusion: _, } - Template::SetInsert | Template::SetRemove | Template::SetContains => { - let key = instance_type(instance, 0)?; - let (lemma, function) = match template { - Template::SetInsert => ("tpl_setInsert", "insertElement"), - Template::SetRemove => ("tpl_setRemove", "removeElement"), - Template::SetContains => ("tpl_setContains", "containsElement"), - Template::MapInsert - | Template::MapRemove - | Template::MapLookup - | Template::MapContains - | Template::MapKeys - | Template::MapValues - | Template::MapFold - | Template::SetUnion - | Template::SetIntersection - | Template::SetDifference - | Template::SetFold - | Template::ListFold - | Template::Iterate - | Template::IterateUntil - | Template::GraphSuccessors - | Template::GraphReachable - | Template::GraphTopological => { - return Err("an element operation of a non-element template".to_owned()); - } - }; - let function = format!("{coll}.{function}"); - ( - format!( - "({} {} {} (fun _ => rfl) {function} {} {} {} {} {head}{} rfl {} {})", - lib(lemma), - self.key_spec(&key, &coll)?, - self.list_bundle(&argument_type(0)?)?, - computed(1), - clause(3, &function), - clause(3, &function), - clause(3, &function), - at(&["tk"]), - value(0)?, - value(1)? - ), - TRUE.to_owned(), - ) + | SemanticTerm::Iff { left: _, right: _ } + | SemanticTerm::Forall { binder: _, body: _ } => Err(format!( + "the formal-only construct `{}` reached the certificate", + super::eligibility::term_key(term) + )), + } + } +} + +/// The closed document types a type mentions. +fn named_types(ty: &SemanticType, out: &mut Vec) { + match ty { + SemanticType::Named { + member: _, + arguments, + } => { + out.push(ty.clone()); + for argument in arguments { + named_types(argument, out); } - Template::SetUnion => { - let key = instance_type(instance, 0)?; - let insert = format!("{coll}.insertElement"); - ( - format!( - "({} {} {} (fun _ => rfl) {insert} {} {} {} {} {head}{} {} {} {})", - lib("tpl_setUnion"), - self.key_spec(&key, &coll)?, - self.list_bundle(&argument_type(0)?)?, - computed(1), - clause(3, &insert), - clause(3, &insert), - clause(3, &insert), - at(&["tk"]), - Self::placed(2), - value(1)?, - value(0)? - ), - TRUE.to_owned(), - ) + } + SemanticType::Option { value: inner } + | SemanticType::List { element: inner } + | SemanticType::Set { element: inner } => named_types(inner, out), + SemanticType::Result { + ok: left, + error: right, + } + | SemanticType::Product { left, right } + | SemanticType::Map { + key: left, + value: right, + } => { + named_types(left, out); + named_types(right, out); + } + SemanticType::Function { parameters, result } => { + for parameter in parameters { + named_types(parameter, out); } - Template::SetIntersection | Template::SetDifference => { - let key = instance_type(instance, 0)?; - let contains = format!("{coll}.containsElement"); - let lemma = match template { - Template::SetIntersection => "tpl_setIntersection", - Template::SetDifference => "tpl_setDifference", - Template::MapInsert - | Template::MapRemove - | Template::MapLookup - | Template::MapContains - | Template::MapKeys - | Template::MapValues - | Template::MapFold - | Template::SetInsert - | Template::SetRemove - | Template::SetContains - | Template::SetUnion - | Template::SetFold - | Template::ListFold - | Template::Iterate - | Template::IterateUntil - | Template::GraphSuccessors - | Template::GraphReachable - | Template::GraphTopological => { - return Err("a filter of a non-filter template".to_owned()); + named_types(result, out); + } + SemanticType::Type + | SemanticType::Parameter { name: _ } + | SemanticType::Nat + | SemanticType::Bool + | SemanticType::Prop + | SemanticType::Unit + | SemanticType::Int + | SemanticType::Int8 + | SemanticType::Int16 + | SemanticType::Int32 + | SemanticType::Int64 + | SemanticType::UInt8 + | SemanticType::UInt16 + | SemanticType::UInt32 + | SemanticType::UInt64 + | SemanticType::String + | SemanticType::Bytes + | SemanticType::Ordering + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure => {} + } +} + +impl<'a> Gen<'a> { + /// The dependency graph of the program's document types: each ADT points + /// at the ADTs its fields mention. + fn adt_graph(&self) -> Result>, String> { + let mut graph: BTreeMap> = BTreeMap::new(); + for (index, adt) in self.layout.adts.iter().enumerate() { + let shape = self.source.document(&adt.ty)?; + let mut dependencies = BTreeSet::new(); + for fields in &shape.fields { + for field in fields { + let mut named = Vec::new(); + named_types(field, &mut named); + for ty in named { + let text = self.source.type_text(&ty); + dependencies.insert( + *self + .adts + .get(&text) + .ok_or_else(|| format!("`{text}` has no ADT in the layout"))?, + ); } - }; - ( - format!( - "({} {} {} (fun _ => rfl) {contains} {} {} {} {} {head}{} {} {} {})", - lib(lemma), - self.key_spec(&key, &coll)?, - self.list_bundle(&argument_type(0)?)?, - computed(1), - clause(3, &contains), - clause(3, &contains), - clause(3, &contains), - at(&["tk"]), - Self::placed(2), - value(0)?, - value(1)? - ), - TRUE.to_owned(), - ) - } - Template::SetFold | Template::ListFold => { - let element = instance_type(instance, 0)?; - let state = instance_type(instance, 1)?; - let step = step()?; - ( - format!( - "({} (ea := {}) (es := {}) {} (fun _ => rfl) {} ({}) {} {} ({}) {head}{} rfl {} {})", - lib("tpl_listFold"), - self.enc(&element)?, - self.enc(&state)?, - self.list_bundle(&argument_type(2)?)?, - value(0)?, - step.fits, - step.index, - step.captures, - step.relation, - at(&["ta", "ts"]), - value(1)?, - value(2)? - ), - format!( - "({} ({}) {} {} {})", - lib("foldFits"), - step.fits, - value(0)?, - value(1)?, - value(2)? - ), - ) - } - Template::Iterate => { - let state = instance_type(instance, 0)?; - let step = step()?; - ( - format!( - "({} (es := {}) {} ({}) {} {} ({}) ({coll}.iterate {}) {} {} {head}{} rfl {} {})", - lib("tpl_iterate"), - self.enc(&state)?, - value(0)?, - step.fits, - step.index, - step.captures, - step.relation, - value(0)?, - computed(1), - computed(2), - at(&["ts"]), - value(1)?, - value(2)? - ), - format!( - "({} ({}) {} {} {})", - lib("iterateFits"), - step.fits, - value(0)?, - value(1)?, - value(2)? - ), - ) - } - Template::IterateUntil => { - let state = instance_type(instance, 0)?; - let step = step()?; - let until = format!("{coll}.iterateUntil"); - ( - format!( - "({} (es := {}) {} (fun _ => rfl) {} (fun _ => rfl) {} ({}) {} {} ({}) ({until} {}) {} {} {} {head}{} rfl {} {})", - lib("tpl_iterateUntil"), - self.enc(&state)?, - self.option_bundle(&SemanticType::Option { - value: Box::new(state.clone()), - })?, - self.enc(&SemanticType::Product { - left: Box::new(state.clone()), - right: Box::new(SemanticType::Bool), - })?, - value(0)?, - step.fits, - step.index, - step.captures, - step.relation, - value(0)?, - computed(1), - clause(2, &until), - clause(3, &until), - at(&["ts"]), - value(1)?, - value(2)? - ), - format!( - "({} ({}) {} {} {})", - lib("untilFits"), - step.fits, - value(0)?, - value(1)?, - value(2)? - ), - ) + } } - Template::GraphSuccessors => { - let node = instance_type(instance, 0)?; - let nodes = SemanticType::List { - element: Box::new(node.clone()), - }; - ( - format!( - "({} {} {} {} (fun _ => rfl) {lookup_entry} {} {} {} {} {head}{} {} {} {})", - lib("tpl_graphSuccessors"), - self.key_spec(&node, &coll)?, - self.list_bundle(&nodes)?, - self.list_bundle(&argument_type(0)?)?, - computed(1), - clause(4, &lookup_entry), - clause(4, &lookup_entry), - clause(4, &lookup_entry), - at(&["tk"]), - Self::placed(2), - value(0)?, - value(1)? - ), - TRUE.to_owned(), - ) + graph.insert(index as u64, dependencies); + } + Ok(graph) + } + + /// Register every container type nested inside a recursive group of + /// document types. + fn nest(&mut self) -> Result<(), String> { + let graph = self.adt_graph()?; + for component in components(&graph) { + let members: BTreeSet = component.iter().copied().collect(); + let recursive = component.len() > 1 + || component + .iter() + .any(|index| graph.get(index).is_some_and(|edges| edges.contains(index))); + if !recursive { + continue; } - Template::GraphReachable | Template::GraphTopological => { - let node = instance_type(instance, 0)?; - let nodes = SemanticType::List { - element: Box::new(node.clone()), - }; - let graph = value(0)?; - let successors = format!("({coll}.graphSuccessors {graph})"); - let common = format!( - "{} {} (fun _ => rfl) {} (fun _ => rfl)", - self.key_spec(&node, &coll)?, - self.list_bundle(&nodes)?, - self.list_bundle(&argument_type(0)?)?, - ); - let fits = format!("({} {coll}.insertElement {graph})", lib("graphFits")); - match template { - Template::GraphReachable => { - let reach = format!("{coll}.reachableFrom"); - ( - format!( - "({} {common} {} {} {graph} {successors} (fun _ => rfl) ({reach} {graph}) {} {} {} {head}{} {} {})", - lib("tpl_graphReachable"), - self.set_ops(&node, &coll)?, - self.look_ops(&node, &nodes, &coll)?, - computed(2), - clause(3, &reach), - clause(5, &reach), - at(&["tk"]), - Self::placed(13), - value(1)? - ), - fits, - ) - } - Template::GraphTopological => { - let topological = format!("{coll}.topological"); - let remove = format!("{coll}.removeElement"); - let contains = format!("{coll}.containsElement"); - ( - format!( - "({} {common} {} (fun _ => rfl) {} {} {graph} {successors} (fun _ => rfl) (⟨{}, {}, {}, (fun _ _ _ _ __h => by simp only [{topological}, __h] <;> rfl), {}⟩ : {} {successors} {contains} {remove} ({topological} {graph})) {head}{} {})", - lib("tpl_graphTopological"), - self.option_bundle(&SemanticType::Option { - value: Box::new(nodes.clone()), - })?, - self.set_ops(&node, &coll)?, - self.look_ops(&node, &nodes, &coll)?, - computed(1), - computed(3), - computed(2), - clause(5, &topological), - lib("TopoOps"), - at(&["tk"]), - Self::placed(12) - ), - fits, - ) - } - Template::MapInsert - | Template::MapRemove - | Template::MapLookup - | Template::MapContains - | Template::MapKeys - | Template::MapValues - | Template::MapFold - | Template::SetInsert - | Template::SetRemove - | Template::SetContains - | Template::SetUnion - | Template::SetIntersection - | Template::SetDifference - | Template::SetFold - | Template::ListFold - | Template::Iterate - | Template::IterateUntil - | Template::GraphSuccessors => { - return Err("a traversal of a non-traversal template".to_owned()); + for index in &component { + let ty = self.layout.adts[*index as usize].ty.clone(); + let shape = self.source.document(&ty)?; + for fields in &shape.fields { + for field in fields { + self.register(field, &members, component[0])?; } } } - }; - let list = operands(proofs); - Ok(Proof { - proof: format!("({} {} {lemma})", lib("conv_call"), list.proof), - fits: and(&list.fits, &fits), - }) + } + Ok(()) } -} -/// Whether a type mentions the type parameter `name`. -fn mentions_parameter(ty: &SemanticType, name: &str) -> bool { - let mut found = false; - let mut stack = vec![ty]; - loop { - let current = match stack.pop() { - Some(current) => current, - None => break, - }; - match current { - SemanticType::Parameter { name: other } => found |= other == name, + /// Register `ty` and its nested containers when it mentions a member of + /// the group; inner containers first, so indices follow dependencies. + fn register( + &mut self, + ty: &SemanticType, + members: &BTreeSet, + group: u64, + ) -> Result<(), String> { + let mut named = Vec::new(); + named_types(ty, &mut named); + let touches = named.iter().any(|named| { + self.adts + .get(&self.source.type_text(named)) + .is_some_and(|index| members.contains(index)) + }); + if !touches { + return Ok(()); + } + match ty { + SemanticType::Named { + member: _, + arguments: _, + } => return Ok(()), SemanticType::Option { value: inner } | SemanticType::List { element: inner } - | SemanticType::Set { element: inner } => stack.push(inner), + | SemanticType::Set { element: inner } => self.register(inner, members, group)?, + SemanticType::Map { key, value } => self.register( + &SemanticType::Product { + left: key.clone(), + right: value.clone(), + }, + members, + group, + )?, SemanticType::Result { ok: left, error: right, } - | SemanticType::Product { left, right } - | SemanticType::Map { - key: left, - value: right, - } => { - stack.push(left); - stack.push(right); + | SemanticType::Product { left, right } => { + self.register(left, members, group)?; + self.register(right, members, group)?; } - SemanticType::Named { - member: _, - arguments, - } => stack.extend(arguments.iter()), - SemanticType::Function { parameters, result } => { - stack.extend(parameters.iter()); - stack.push(result); + SemanticType::Function { + parameters: _, + result: _, } - SemanticType::Type + | SemanticType::Type + | SemanticType::Prop + | SemanticType::Parameter { name: _ } | SemanticType::Nat | SemanticType::Bool - | SemanticType::Prop | SemanticType::Unit | SemanticType::Int | SemanticType::Int8 @@ -7243,126 +2792,127 @@ fn mentions_parameter(ty: &SemanticType, name: &str) -> bool { | SemanticType::Bytes | SemanticType::Ordering | SemanticType::ContractViolation - | SemanticType::ReasoningFailure => {} + | SemanticType::ReasoningFailure => { + return Err(format!( + "`{}` cannot hold a document type in a field", + self.source.type_text(ty) + )); + } } + let text = self.source.type_text(ty); + let index = self.nested.len() as u64; + self.nested.entry(text).or_insert(Nested { + index, + ty: ty.clone(), + group, + }); + Ok(()) + } +} + +impl<'a> Gen<'a> {} + +/// The Lean type of an integer literal's representation. +const fn integer_lean(representation: SemanticInteger) -> &'static str { + match representation { + SemanticInteger::Int => "Int", + SemanticInteger::Int8 => "Int8", + SemanticInteger::Int16 => "Int16", + SemanticInteger::Int32 => "Int32", + SemanticInteger::Int64 => "Int64", + SemanticInteger::UInt8 => "UInt8", + SemanticInteger::UInt16 => "UInt16", + SemanticInteger::UInt32 => "UInt32", + SemanticInteger::UInt64 => "UInt64", } - found +} + +/// The key type, value type, and node type of a template instance. +fn instance_type(instance: &[SemanticType], position: usize) -> Result { + instance + .get(position) + .cloned() + .ok_or_else(|| format!("a template instance lacks type argument {position}")) } impl Gen<'_> { - /// A source `match` with rendered alternatives, elaborated so that the - /// pinned Lean reuses the source's own matcher. - /// - /// Lean shares one auxiliary matcher among matches with the same - /// discriminant types and patterns, and the kernel compares two matches - /// on an unknown discriminant only when they share it. A match in a - /// generic definition has a matcher abstracted over the type parameters - /// its discriminant type mentions; the instance's match is therefore - /// written inside the same abstraction and applied to the type - /// arguments, its alternatives passed in as functions. - #[allow(clippy::too_many_arguments)] - fn select( - &self, - ctx: &Ctx, - scrutinee: &SemanticTerm, - scrutinee_src: &str, - branches: &[SemanticBranch], - alternatives: &[String], - result: &str, - hypothesis: Option<&str>, - ) -> Result { - let generic = self - .source - .infer(scrutinee, &ctx.generics(), &ctx.generic)?; - let abstracted: Vec<&(String, SemanticType)> = ctx - .parameters - .iter() - .filter(|(name, _)| mentions_parameter(&generic, name)) - .collect(); - let head = |discriminant: &str| match hypothesis { - Some(name) => format!("match (generalizing := false) {name} : {discriminant} with"), - None => format!("match {discriminant} with"), - }; - if abstracted.is_empty() { - let mut out = format!("({}", head(scrutinee_src)); - for (branch, alternative) in branches.iter().zip(alternatives) { - out.push_str(&format!( - " | {} => {alternative}", - self.pattern(branch, &ctx.site)? - )); + /// The target type of a closed source type, with document types read + /// from the layout. + fn target_ty(&self, ty: &SemanticType) -> Result { + Ok(match ty { + SemanticType::Nat => Ty::Nat, + SemanticType::Bool => Ty::Bool, + SemanticType::ContractViolation | SemanticType::ReasoningFailure => Ty::Pair { + left: Box::new(Ty::Bool), + right: Box::new(Ty::Bool), + }, + SemanticType::Unit => Ty::Unit, + SemanticType::Int => Ty::Int, + SemanticType::Int8 + | SemanticType::Int16 + | SemanticType::Int32 + | SemanticType::Int64 + | SemanticType::UInt8 + | SemanticType::UInt16 + | SemanticType::UInt32 + | SemanticType::UInt64 => Ty::Fixed { + width: fixed_kind(ty).ok_or("a fixed width without a kind")?, + }, + SemanticType::String => Ty::String, + SemanticType::Bytes => Ty::Bytes, + SemanticType::Ordering => Ty::Ordering, + SemanticType::Option { value } => Ty::Option { + value: Box::new(self.target_ty(value)?), + }, + SemanticType::Result { ok, error } => Ty::Result { + ok: Box::new(self.target_ty(ok)?), + error: Box::new(self.target_ty(error)?), + }, + SemanticType::List { element } | SemanticType::Set { element } => Ty::List { + element: Box::new(self.target_ty(element)?), + }, + SemanticType::Product { left, right } => Ty::Pair { + left: Box::new(self.target_ty(left)?), + right: Box::new(self.target_ty(right)?), + }, + SemanticType::Map { key, value } => Ty::List { + element: Box::new(Ty::Pair { + left: Box::new(self.target_ty(key)?), + right: Box::new(self.target_ty(value)?), + }), + }, + SemanticType::Function { parameters, result } => Ty::Fn { + parameters: parameters + .iter() + .map(|parameter| self.target_ty(parameter)) + .collect::, _>>()?, + result: Box::new(self.target_ty(result)?), + }, + SemanticType::Named { + member: _, + arguments: _, + } => { + let text = self.source.type_text(ty); + Ty::Adt { + index: *self + .adts + .get(&text) + .ok_or_else(|| format!("`{text}` has no ADT in the layout"))?, + } } - out.push(')'); - return Ok(out); - } - let closed = self.source.infer(scrutinee, &ctx.sources(), &ctx.site)?; - let mut binders = String::new(); - for (name, _) in &abstracted { - binders.push_str(&format!(" ({} : Type)", identifier(name))); - } - binders.push_str(&format!(" (__a : {})", self.render_type(&generic, true)?)); - let mut arms = String::new(); - let mut arguments = String::new(); - for (position, (branch, alternative)) in branches.iter().zip(alternatives).enumerate() { - let (_, generic_fields) = self.source.branch(branch, &generic, &ctx.generic)?; - let (_, closed_fields) = self.source.branch(branch, &closed, &ctx.site)?; - let pattern = self.pattern(branch, &ctx.site)?; - let mut domain = String::new(); - let mut parameters = String::new(); - let mut applied = String::new(); - for (binder, (generic_ty, closed_ty)) in branch - .binders - .iter() - .zip(generic_fields.iter().zip(&closed_fields)) - { - let name = identifier(binder); - domain.push_str(&format!( - " ({name} : {})", - self.render_type(generic_ty, true)? + SemanticType::Type | SemanticType::Prop | SemanticType::Parameter { name: _ } => { + return Err(format!( + "`{}` has no runtime realization", + self.source.type_text(ty) )); - parameters.push_str(&format!(" ({name} : {})", self.ty(closed_ty)?)); - applied.push_str(&format!(" {name}")); - } - let continuation = match hypothesis { - Some(name) => { - parameters.push_str(&format!(" {name}")); - applied.push_str(&format!(" {name}")); - format!("∀{domain}, __a = {pattern} -> {result}") - } - None if domain.is_empty() => result.to_owned(), - None => format!("∀{domain}, {result}"), - }; - binders.push_str(&format!(" (__k{position} : {continuation})")); - arms.push_str(&format!(" | {pattern} => __k{position}{applied}")); - if parameters.is_empty() { - arguments.push_str(&format!(" ({alternative})")); - } else { - arguments.push_str(&format!(" (fun{parameters} => {alternative})")); } - } - // The result type is written on the `match`: the template is a - // function elaborated before its arguments, so its body has no - // expected type, and Lean postpones such a `match`. A caller that - // checks a type that mentions it (a fold's step relation, against the - // step function) then fails on the pending metavariable. - let mut out = format!("((fun{binders} => ({}{arms} : {result}))", head("__a")); - for (_, argument) in &abstracted { - out.push_str(&format!(" {}", self.ty(argument)?)); - } - out.push_str(&format!(" {scrutinee_src}{arguments})")); - Ok(out) + }) } } -// --- The boundary (SPEC.md §17.17) ------------------------------------------ +impl Gen<'_> {} -/// One validator's four statements: its definition, its proposition, the -/// theorem that it decides the proposition, and its relation; and, for a -/// container of a recursive group, the theorem that the group's recursion -/// states the library's proposition. -struct Stated { - items: [String; 4], - corollary: String, -} +// --- The boundary (SPEC.md §17.17) ------------------------------------------ /// The components of a map, product, or result type. fn parts(ty: &SemanticType) -> Result<(SemanticType, SemanticType), String> { @@ -7640,64 +3190,6 @@ impl Gen<'_> { }) } - /// Certificate A's boundary: every validator's definition, proposition, - /// decision theorem, and relation, in dependency order, a recursive - /// group's in mutual blocks; then the entry's relation and its two - /// outcomes. - fn boundary( - &self, - signature: &Signature, - names: &[String], - parameters: &[SemanticType], - ) -> Result { - self.verify_boundary(parameters)?; - let checks = self.checks(); - let adt_graph = self.adt_graph()?; - let graph: BTreeMap> = checks - .iter() - .map(|(index, (_, _, kind))| (*index, checked(kind).into_iter().collect())) - .collect(); - let mut out = String::new(); - for component in components(&graph) { - let mut grouped = Vec::new(); - for index in &component { - let (ty, _, kind) = checks[index]; - grouped.push(self.grouped(ty, kind, &adt_graph)?); - } - let recursive = grouped.iter().any(|grouped| *grouped); - if (recursive && !grouped.iter().all(|grouped| *grouped)) - || (!recursive && component.len() > 1) - { - return Err(format!( - "the validators {component:?} call one another outside a recursive group" - )); - } - let mut members = component.clone(); - members.sort_unstable(); - let mut items: [Vec; 4] = [Vec::new(), Vec::new(), Vec::new(), Vec::new()]; - let mut corollaries = String::new(); - for index in members { - let (ty, module, kind) = checks[&index]; - let stated = self.stated(index, ty, module, kind, recursive)?; - for (slot, text) in items.iter_mut().zip(stated.items) { - slot.push(text); - } - corollaries.push_str(&stated.corollary); - } - for slot in &items { - out.push_str(&mutual_block(slot)); - } - out.push_str(&corollaries); - } - match self.entry_of() { - Some((entry, validators)) => { - out.push_str(&self.entry_section(entry, validators, signature, names)?); - } - None => {} - } - Ok(out) - } - /// Whether a closed type can hold a map, set, or graph, whose strictly /// ascending order §17.12 states as an invariant: decided here from the /// source type alone, by this generator's own recursion, so that what @@ -7909,433 +3401,4 @@ impl Gen<'_> { } Ok(()) } - - /// One validator's statements. - #[allow(clippy::too_many_lines)] - fn stated( - &self, - k: u64, - ty: &SemanticType, - module: &str, - kind: &Validation, - recursive: bool, - ) -> Result { - let p = LIBRARY; - let value = format!("{SYNTAX}.Value"); - let lean = self.ty(ty)?; - let enc = self.enc(ty)?; - let coll = format!("{}.LexLeanCollections", self.lean_module(module)?); - let statements = |valid: &str, inv: &str, viff: &str, vrel: &str| { - Stated { - items: [ - format!("def __valid_{k} : {lean} -> Bool{valid}\n\n"), - format!("def __inv_{k} : {lean} -> Prop{inv}\n\n"), - format!( - "theorem __viff_{k} : ∀ (__v : {lean}), __valid_{k} __v = Bool.true ↔ __inv_{k} __v{viff}\n\n" - ), - format!( - "theorem __vrel_{k} : ∀ (__v : {lean}), {p}.FunRel __prog {k} [({enc} __v)] ({p}.Rel Bool.true ({value}.bool (__valid_{k} __v))){vrel}\n\n" - ), - ], - corollary: String::new(), - } - }; - let unfold = format!("by rw [__valid_{k}.eq_def, __inv_{k}.eq_def]; exact"); - let unfold_valid = format!("by rw [__valid_{k}.eq_def]; exact"); - let hit_true = format!( - "{p}.funRel_intro rfl rfl ({p}.conv_match ({p}.conv_var rfl) ({p}.convA_hit rfl rfl ({p}.conv_build {p}.convL_nil {p}.construct_true)))" - ); - let call = |validator: &u64, operand: &str, argument: &str| { - format!( - "{p}.conv_call ({p}.convL_cons {operand} {p}.convL_nil) (__vrel_{validator} {argument})" - ) - }; - let var = format!("({p}.conv_var rfl)"); - let falsity = format!("(fun _ => {p}.conv_build {p}.convL_nil {p}.construct_false)"); - match (kind, recursive) { - (Validation::Document { fields }, true | false) => { - self.document_stated(k, ty, fields, recursive) - } - (Validation::Trivial, false) => Ok(statements( - " :=\n fun _ => Bool.true", - " :=\n fun _ => _root_.True", - &format!(" :=\n fun __v => {p}.validTrue_inv __v"), - &format!(" :=\n {p}.tpl_validTrue {enc} rfl"), - )), - (Validation::Set, false) => { - let key = element_of(ty)?; - let cmp = self.key_order(&key, &coll)?; - Ok(statements( - &format!(" :=\n {p}.ascSet {cmp}"), - &format!(" :=\n {p}.InvSet {cmp}"), - &format!(" :=\n {p}.ascSet_inv {cmp}"), - &format!( - " :=\n {p}.tpl_validSet {} {} (fun _ => rfl) rfl", - self.key_spec(&key, &coll)?, - self.list_bundle(ty)? - ), - )) - } - (Validation::Map { value: v }, false) => { - let (key, _) = parts(ty)?; - let cmp = self.key_order(&key, &coll)?; - Ok(statements( - &format!(" :=\n {p}.ascMap {cmp} __valid_{v}"), - &format!(" :=\n {p}.InvMap {cmp} __inv_{v}"), - &format!(" :=\n {p}.ascMap_inv {cmp} __valid_{v} __inv_{v} __viff_{v}"), - &format!( - " :=\n {p}.tpl_validMap {} __valid_{v} {} (fun _ => rfl) __vrel_{v} rfl", - self.key_spec(&key, &coll)?, - self.list_bundle(ty)? - ), - )) - } - (Validation::List { element: e }, false) => Ok(statements( - &format!(" :=\n {p}.validList __valid_{e}"), - &format!(" :=\n {p}.InvList __inv_{e}"), - &format!(" :=\n {p}.validList_inv __valid_{e} __inv_{e} __viff_{e}"), - &format!( - " :=\n {p}.tpl_validList __valid_{e} {} (fun _ => rfl) __vrel_{e} rfl", - self.list_bundle(ty)? - ), - )), - (Validation::Option { value: v }, false) => Ok(statements( - &format!(" :=\n {p}.validOption __valid_{v}"), - &format!(" :=\n {p}.InvOption __inv_{v}"), - &format!(" :=\n {p}.validOption_inv __valid_{v} __inv_{v} __viff_{v}"), - &format!(" :=\n {p}.tpl_validOption __valid_{v} __vrel_{v} rfl"), - )), - (Validation::Pair { left: a, right: b }, false) => Ok(statements( - &format!(" :=\n {p}.validPair __valid_{a} __valid_{b}"), - &format!(" :=\n {p}.InvPair __inv_{a} __inv_{b}"), - &format!( - " :=\n {p}.validPair_inv __valid_{a} __valid_{b} __inv_{a} __inv_{b} __viff_{a} __viff_{b}" - ), - &format!(" :=\n {p}.tpl_validPair __valid_{a} __valid_{b} __vrel_{a} __vrel_{b} rfl"), - )), - (Validation::Result { ok: a, error: b }, false) => Ok(statements( - &format!(" :=\n {p}.validExcept __valid_{b} __valid_{a}"), - &format!(" :=\n {p}.InvExcept __inv_{b} __inv_{a}"), - &format!( - " :=\n {p}.validExcept_inv __valid_{b} __valid_{a} __inv_{b} __inv_{a} __viff_{b} __viff_{a}" - ), - &format!(" :=\n {p}.tpl_validResult __valid_{b} __valid_{a} __vrel_{a} __vrel_{b} rfl"), - )), - // A container nested in a recursive group: the group's own - // structural recursion, as its encoder is. - (Validation::List { element: e }, true) => { - let mut stated = statements( - &format!( - "\n | [] => Bool.true\n | __x0 :: __x1 => if __valid_{e} __x0 then __valid_{k} __x1 else Bool.false" - ), - &format!("\n | [] => _root_.True\n | __x0 :: __x1 => __inv_{e} __x0 ∧ __inv_{k} __x1"), - &format!( - "\n | [] => {unfold} {p}.trueIff\n | __x0 :: __x1 => {unfold} {p}.condAnd_iff (__viff_{e} __x0) (__viff_{k} __x1)" - ), - &format!( - "\n | [] => {unfold_valid} {hit_true}\n | __x0 :: __x1 => {unfold_valid} {p}.FunRel.fits_eq ({p}.funRel_intro rfl rfl ({p}.conv_match {var} ({p}.convA_miss rfl ({p}.convA_hit rfl rfl ({p}.conv_cond (fun _ => Bool.true) (fun __c => {value}.bool (if __c then __valid_{k} __x1 else Bool.false)) (__valid_{e} __x0) ({}) (fun _ => {}) {falsity}))))) rfl", - call(e, &var, "__x0"), - call(&k, &var, "__x1") - ), - ); - stated.corollary = format!( - "theorem __vinv_{k} : ∀ (__v : {lean}), __inv_{k} __v ↔ {p}.InvList __inv_{e} __v :=\n {p}.invList_eqs __inv_{e} __inv_{k} (by rw [__inv_{k}.eq_def]) (fun _ _ => by rw [__inv_{k}.eq_def])\n\n" - ); - Ok(stated) - } - (Validation::Option { value: v }, true) => { - let mut stated = statements( - &format!("\n | none => Bool.true\n | some __x0 => __valid_{v} __x0"), - &format!("\n | none => _root_.True\n | some __x0 => __inv_{v} __x0"), - &format!( - "\n | none => {unfold} {p}.trueIff\n | some __x0 => {unfold} __viff_{v} __x0" - ), - &format!( - "\n | none => {unfold_valid} {hit_true}\n | some __x0 => {unfold_valid} {p}.FunRel.fits_eq ({p}.funRel_intro rfl rfl ({p}.conv_match {var} ({p}.convA_miss rfl ({p}.convA_hit rfl rfl ({}))))) rfl", - call(v, &var, "__x0") - ), - ); - stated.corollary = format!( - "theorem __vinv_{k} : ∀ (__v : {lean}), __inv_{k} __v ↔ {p}.InvOption __inv_{v} __v\n | none => by rw [__inv_{k}.eq_def]; exact Iff.rfl\n | some _ => by rw [__inv_{k}.eq_def]; exact Iff.rfl\n\n" - ); - Ok(stated) - } - (Validation::Pair { left: a, right: b }, true) => { - let mut stated = statements( - &format!( - "\n | (__x0, __x1) => if __valid_{a} __x0 then __valid_{b} __x1 else Bool.false" - ), - &format!("\n | (__x0, __x1) => __inv_{a} __x0 ∧ __inv_{b} __x1"), - &format!( - "\n | (__x0, __x1) => {unfold} {p}.condAnd_iff (__viff_{a} __x0) (__viff_{b} __x1)" - ), - &format!( - "\n | (__x0, __x1) => {unfold_valid} {p}.FunRel.fits_eq ({p}.funRel_intro rfl rfl ({p}.conv_cond (fun _ => Bool.true) (fun __c => {value}.bool (if __c then __valid_{b} __x1 else Bool.false)) (__valid_{a} __x0) ({}) (fun _ => {}) {falsity})) rfl", - call(a, &format!("({p}.conv_first {var})"), "__x0"), - call(b, &format!("({p}.conv_second {var})"), "__x1") - ), - ); - stated.corollary = format!( - "theorem __vinv_{k} : ∀ (__v : {lean}), __inv_{k} __v ↔ {p}.InvPair __inv_{a} __inv_{b} __v\n | (_, _) => by rw [__inv_{k}.eq_def]; exact Iff.rfl\n\n" - ); - Ok(stated) - } - (Validation::Result { ok: a, error: b }, true) => { - let mut stated = statements( - &format!( - "\n | Except.error __x0 => __valid_{b} __x0\n | Except.ok __x0 => __valid_{a} __x0" - ), - &format!( - "\n | Except.error __x0 => __inv_{b} __x0\n | Except.ok __x0 => __inv_{a} __x0" - ), - &format!( - "\n | Except.error __x0 => {unfold} __viff_{b} __x0\n | Except.ok __x0 => {unfold} __viff_{a} __x0" - ), - &format!( - "\n | Except.error __x0 => {unfold_valid} {p}.FunRel.fits_eq ({p}.funRel_intro rfl rfl ({p}.conv_match {var} ({p}.convA_miss rfl ({p}.convA_hit rfl rfl ({}))))) rfl\n | Except.ok __x0 => {unfold_valid} {p}.FunRel.fits_eq ({p}.funRel_intro rfl rfl ({p}.conv_match {var} ({p}.convA_hit rfl rfl ({})))) rfl", - call(b, &var, "__x0"), - call(a, &var, "__x0") - ), - ); - stated.corollary = format!( - "theorem __vinv_{k} : ∀ (__v : {lean}), __inv_{k} __v ↔ {p}.InvExcept __inv_{b} __inv_{a} __v\n | Except.error _ => by rw [__inv_{k}.eq_def]; exact Iff.rfl\n | Except.ok _ => by rw [__inv_{k}.eq_def]; exact Iff.rfl\n\n" - ); - Ok(stated) - } - // §17.12's positivity rule keeps a recursive occurrence out of - // a map or set, so neither is nested in a recursive group. - (Validation::Trivial | Validation::Set | Validation::Map { value: _ }, true) => { - Err(format!( - "the validator of `{}` cannot belong to a recursive group", - self.source.type_text(ty) - )) - } - } - } - - /// A document type's validator: per constructor, its checked fields in - /// order, as the lowering's arms test them. - fn document_stated( - &self, - k: u64, - ty: &SemanticType, - fields: &[Vec>], - recursive: bool, - ) -> Result { - let p = LIBRARY; - let value = format!("{SYNTAX}.Value"); - let shape = self.source.document(ty)?; - let lean = self.ty(ty)?; - let enc = self.enc(ty)?; - let var = format!("({p}.conv_var rfl)"); - let falsity = format!("(fun _ => {p}.conv_build {p}.convL_nil {p}.construct_false)"); - // The value, proposition, decision, and relation of the checks of - // `terms` from position `at` on. - let chain = |terms: &[(String, Option)]| -> [String; 4] { - let mut out = [ - TRUE.to_owned(), - "_root_.True".to_owned(), - format!("{p}.trueIff"), - format!("{p}.conv_build {p}.convL_nil {p}.construct_true"), - ]; - for (term, check) in terms.iter().rev() { - match check { - Some(c) => { - let [valid, inv, viff, vrel] = out; - out = [ - format!("(if __valid_{c} {term} then {valid} else Bool.false)"), - format!("(__inv_{c} {term} ∧ {inv})"), - format!("({p}.condAnd_iff (__viff_{c} {term}) {viff})"), - format!( - "({p}.conv_cond (fun _ => Bool.true) (fun __c => {value}.bool (if __c then {valid} else Bool.false)) (__valid_{c} {term}) ({p}.conv_call ({p}.convL_cons {var} {p}.convL_nil) (__vrel_{c} {term})) (fun _ => {vrel}) {falsity})" - ), - ]; - } - None => {} - } - } - out - }; - if fields.len() != shape.fields.len() { - return Err(format!( - "the validator of `{lean}` checks {} constructors of {}", - fields.len(), - shape.fields.len() - )); - } - if shape.kind == "inductive" || recursive { - let owner = lean - .trim_start_matches('(') - .split(' ') - .next() - .unwrap_or_default() - .trim_end_matches(')') - .to_owned(); - let mut items = [ - format!("def __valid_{k} : {lean} -> Bool\n"), - format!("def __inv_{k} : {lean} -> Prop\n"), - format!( - "theorem __viff_{k} : ∀ (__v : {lean}), __valid_{k} __v = Bool.true ↔ __inv_{k} __v\n" - ), - format!( - "theorem __vrel_{k} : ∀ (__v : {lean}), {p}.FunRel __prog {k} [({enc} __v)] ({p}.Rel Bool.true ({value}.bool (__valid_{k} __v)))\n" - ), - ]; - for (position, ((constructor, types), checks)) in shape - .names - .iter() - .zip(&shape.fields) - .zip(fields) - .enumerate() - { - if types.len() != checks.len() { - return Err(format!("`{lean}.{constructor}`: a check per field")); - } - let binders: Vec = (0..types.len()) - .map(|field| format!("__x{field}")) - .collect(); - let pattern = if shape.kind == "inductive" { - format!( - "{owner}.{}{}", - identifier(constructor), - binders - .iter() - .map(|binder| format!(" {binder}")) - .collect::() - ) - } else { - format!("⟨{}⟩", binders.join(", ")) - }; - let terms: Vec<(String, Option)> = binders - .iter() - .cloned() - .zip(checks.iter().copied()) - .collect(); - let [valid, inv, viff, vrel] = chain(&terms); - let misses = format!("{p}.convA_miss rfl (").repeat(position); - let closing = ")".repeat(position); - items[0].push_str(&format!(" | {pattern} => {valid}\n")); - items[1].push_str(&format!(" | {pattern} => {inv}\n")); - items[2].push_str(&format!( - " | {pattern} => by rw [__valid_{k}.eq_def, __inv_{k}.eq_def]; exact {viff}\n" - )); - items[3].push_str(&format!( - " | {pattern} => by rw [__valid_{k}.eq_def]; exact {p}.FunRel.fits_eq ({p}.funRel_intro rfl rfl ({p}.conv_match {var} ({misses}{p}.convA_hit rfl rfl {vrel}{closing}))) rfl\n" - )); - } - for item in &mut items { - item.push('\n'); - } - return Ok(Stated { - items, - corollary: String::new(), - }); - } - let checks = fields - .first() - .ok_or_else(|| format!("`{lean}` has no constructor"))?; - if shape.field_names.len() != checks.len() { - return Err(format!("`{lean}`: a check per field")); - } - let terms: Vec<(String, Option)> = shape - .field_names - .iter() - .map(|name| format!("(__s).{}", identifier(name))) - .zip(checks.iter().copied()) - .collect(); - let [valid, inv, viff, vrel] = chain(&terms); - Ok(Stated { - items: [ - format!("def __valid_{k} (__s : {lean}) : Bool :=\n {valid}\n\n"), - format!("def __inv_{k} (__s : {lean}) : Prop :=\n {inv}\n\n"), - format!( - "theorem __viff_{k} : ∀ (__s : {lean}), __valid_{k} __s = Bool.true ↔ __inv_{k} __s :=\n fun __s => {viff}\n\n" - ), - format!( - "theorem __vrel_{k} : ∀ (__s : {lean}), {p}.FunRel __prog {k} [({enc} __s)] ({p}.Rel Bool.true ({value}.bool (__valid_{k} __s))) :=\n fun __s => {p}.FunRel.fits_eq ({p}.funRel_intro rfl rfl ({p}.conv_match {var} ({p}.convA_hit rfl rfl {vrel}))) rfl\n\n" - ), - ], - corollary: String::new(), - }) - } - - /// The entry's statements: its observation, the conjunction of §17.12's - /// propositions of its validated parameters (`accepts`), its relation, - /// and its two outcomes: the root's observation as `some` when the - /// propositions hold, and `none` when one fails. - fn entry_section( - &self, - entry: u64, - validators: &[Option], - signature: &Signature, - names: &[String], - ) -> Result { - let p = LIBRARY; - let module = &self.module; - let value = format!("{SYNTAX}.Value"); - if validators.len() != names.len() { - return Err("the entry validates a parameter list of another length".to_owned()); - } - let binders = signature.fits_binders.join(" "); - let applied = names.join(" "); - let var = format!("({p}.conv_var rfl)"); - let mut fits = signature.fits_applied.clone(); - let mut outcome = format!("({value}.some {})", signature.value); - let mut accepts = "_root_.True".to_owned(); - let arguments = names.iter().fold(format!("{p}.convL_nil"), |rest, _| { - format!("({p}.convL_cons {var} {rest})") - }); - let mut proof = format!( - "{p}.conv_build ({p}.convL_cons ({p}.conv_call {arguments} (__rel_0 {applied})) {p}.convL_nil) {p}.construct_some" - ); - let mut checked_names = Vec::new(); - for (validator, name) in validators.iter().zip(names).rev() { - match validator { - Some(c) => { - proof = format!( - "{p}.conv_cond (fun __c => if __c then {fits} else Bool.true) (fun __c => if __c then {outcome} else {value}.none) (__valid_{c} {name}) ({p}.conv_call ({p}.convL_cons {var} {p}.convL_nil) (__vrel_{c} {name})) (fun _ => {p}.Conv.fits_eq ({proof}) (by simp)) (fun _ => {p}.conv_build {p}.convL_nil {p}.construct_none)" - ); - fits = format!("(if __valid_{c} {name} then {fits} else Bool.true)"); - outcome = format!("(if __valid_{c} {name} then {outcome} else {value}.none)"); - accepts = format!("(__inv_{c} {name} ∧ {accepts})"); - checked_names.push((*c, name.clone())); - } - None => {} - } - } - checked_names.reverse(); - // With every validator true, each proposition holds; a validator - // that is false makes the entry return `none`. - let mut uses = String::new(); - let mut path = "__h".to_owned(); - let mut witnesses = Vec::new(); - let mut refusal = String::new(); - for (position, (c, name)) in checked_names.iter().enumerate() { - uses.push_str(&format!(", (__viff_{c} {name}).mpr {path}.1")); - path = format!("{path}.2"); - witnesses.push(format!("(__viff_{c} {name}).mp __c{position}")); - let indent = " ".repeat(position + 1); - let lead = " ".repeat(usize::from(position == 0)); - refusal.push_str(&format!( - "{lead}cases __c{position} : __valid_{c} {name}\n{indent}· simp\n{indent}· " - )); - } - refusal.push_str(&format!( - "exact _root_.absurd ⟨{}, _root_.trivial⟩ __h\n", - witnesses.join(", ") - )); - Ok(format!( - "def entryFits {binders} : Bool :=\n {fits}\n\n\ - def entryValue {binders} : {value} :=\n {outcome}\n\n\ - /-- The entry's observation. -/\n\ - def denoteEntry {binders} : {p}.Obs :=\n _root_.cond ({module}.entryFits {applied}) ({p}.Obs.value ({module}.entryValue {applied})) {p}.Obs.overflow\n\n\ - /-- §17.12's invariants of the validated parameters. -/\n\ - def accepts {binders} : Prop :=\n {accepts}\n\n\ - theorem entry {binders} : {p}.RunConv __prog {entry} {} ({module}.denoteEntry {applied}) :=\n {p}.run_of_funRel ({p}.FunRel.fits_eq ({p}.funRel_intro rfl rfl ({proof})) (by simp [{module}.entryFits]))\n\n\ - theorem entry_accepts {binders} (__h : {module}.accepts {applied}) : {module}.denoteEntry {applied} = {p}.someObs ({module}.denote {applied}) := by\n unfold {module}.accepts at __h\n unfold {module}.denoteEntry {module}.entryFits {module}.entryValue {module}.denote\n simp only [_root_.if_true{uses}]\n cases {} <;> rfl\n\n\ - theorem entry_refuses {binders} (__h : ¬ {module}.accepts {applied}) : {module}.denoteEntry {applied} = {p}.Obs.value {value}.none := by\n unfold {module}.accepts at __h\n unfold {module}.denoteEntry {module}.entryFits {module}.entryValue\n{refusal}\n", - signature.arguments, - signature.fits_applied, - )) - } } diff --git a/crates/lexlean/src/production/certificate/data_a.rs b/crates/lexlean/src/production/certificate/data_a.rs index 021bdd8e2..c6fe9a27d 100644 --- a/crates/lexlean/src/production/certificate/data_a.rs +++ b/crates/lexlean/src/production/certificate/data_a.rs @@ -201,14 +201,16 @@ impl<'g, 'a> Writer<'g, 'a> { // construct's declared set as an upper bound; the audit finds // none for it, as it finds for any definition that proves // nothing, so the exact set is empty. - let derived = matches!(declaration, crate::ir::semantic::SemanticDeclaration::Definition { .. }) - && !linked.semantic.declarations.iter().any(|source| { - source.name() == declaration.name() - && matches!( - source, - crate::ir::semantic::SemanticDeclaration::Definition { .. } - ) - }); + let derived = matches!( + declaration, + crate::ir::semantic::SemanticDeclaration::Definition { .. } + ) && !linked.semantic.declarations.iter().any(|source| { + source.name() == declaration.name() + && matches!( + source, + crate::ir::semantic::SemanticDeclaration::Definition { .. } + ) + }); if derived { axioms.clear(); } From b3682defe26eb5b50bfae1f0322db3085fc33357 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 19:13:46 +0000 Subject: [PATCH 19/41] #89 stage 3: SPEC, CHANGELOG, and the source scan of SP-15 Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- CHANGELOG.md | 24 +++++++-- SPEC.md | 42 +++++++++++++++- crates/conformance/src/cases/preservation.rs | 53 ++++++++++++++++++++ 3 files changed, 114 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b07b1d93..e8ff22c1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,24 @@ versions, and the entries below say what each tag does and does not claim. ## Unreleased +- Certificate A as kernel-term data (SPEC.md §17.17, `SP-15`; stage 3 of #89). + The preservation certificate of every production root is no longer written as + Lean text by `production/certificate.rs`: the compiler builds it as a native + core module of schema `lexlean/core-module/2` on the vehicle of + certificate E (`production/kernel.rs`), the program literal, encoders, width + predicates, relation theorems, validators, and entry as rows, each lemma + applied with every argument written, structural and well-founded recursion as + recursor and `WellFounded.fix` terms with the source's own decrease theorems, + and the collection templates' clause proofs as kernel terms in place of + tactics. The verify record holds the module in the authoring notation + (`R.lex.tex` in place of `R.lean`), which reads back to the data byte + for byte, and `preservation.json` binds those bytes. The statements of the 57 + certificates A of the four corpora are those the text stated, up to the + kernel's definitional equality + (`tests/preservation-certificates/A-STATEMENTS.md`). Roughly 5 000 lines of the + text generator are gone from `production/certificate.rs`. Certificate B is + still Lean text. No diagnostic code is new; the register gains `SP-15` (384 + rows). - Certificate E as kernel-term data (SPEC.md §17.17, `SP-14`; stage 1 of #89, stage 5 of #27). The composition certificate of every production root is no longer written as Lean text by `production/certificate.rs`: the compiler @@ -29,9 +47,9 @@ versions, and the entries below say what each tag does and does not claim. policies, forbidden constants) and the audit module prints and checks the axioms of every one of its rows. The statements of the 76 certificates E of the four corpora are those the text stated, kernel term for kernel term - (`tests/preservation-certificates/E-STATEMENTS.md`). Certificates A and B are - still Lean text, so the data certificate is rendered outside the module - system until they follow. Languages 1.0 and 1.1 have no certificates and are + (`tests/preservation-certificates/E-STATEMENTS.md`). Certificate B (and, until + stage 3 of #89, certificate A) is still Lean text, so the data certificate is + rendered outside the module system until they follow. Languages 1.0 and 1.1 have no certificates and are untouched. No diagnostic code is new; the register gains `SP-14` (383 rows with `SP-13`, `RB-09`, and `PX-13`). - Child deadlines remain active while reading inherited output pipes after the direct child exits (#132). The process group stays registered until diff --git a/SPEC.md b/SPEC.md index a54be8cc1..c396101ac 100644 --- a/SPEC.md +++ b/SPEC.md @@ -5275,7 +5275,7 @@ The well-typedness theorems of a recursive group are the group's recursor type for the `j`-th container the kernel nests, in the order it nests them), an induction hypothesis standing where the text recursed. The one core backend renders the module to the Lean verification compiles, with `import` -lines and no `module` header while certificates A and B are Lean text (a +lines and no `module` header while certificate B is Lean text (a `module` cannot import them), and the verify record holds the module in the authoring notation (§17.21.8), at the module's path with the extension `.lex.tex`, which reads back to the data byte for byte; `preservation.json` @@ -5291,6 +5291,44 @@ corpora this way, reads each record back to its data, verifies the production example under the pinned Lean, and plants a wrong lemma, a changed statement, a forged crate reference, and an added axiom, each refused. +**Certificate A as data.** Certificate A is kernel-term data as well. The +compiler builds it as a native core module of schema `lexlean/core-module/2` +from the lowered program, the layout, the semantic source of the root's +closure, and the recorded types of the library's lemmas; no Lean text template +for it remains in the compiler. Its rows are the program literal `__prog`, the +encoders, a width predicate `__fits_k` and a relation theorem `__rel_k` for each +function of the closure, the boundary validators and the entry when the root has +them, then `denote` and `root`. A relation theorem is derived node by node +alongside the lowering of the function: a library lemma is applied at the +operator, operand expressions, and evaluation environment its node has in the +program, and what the lemma's type leaves implicit is stated, either from the +program or from the types of the arguments. A definition's denotation appears in +the statements as the definition itself, so its body is never unfolded to +compare it. A recursive definition's predicate and theorem are written with the +eliminator its recursion has: the recursor of the argument's type for structural +recursion (a mutual group on one type packing its members as a product, a group +with nested containers by the recursor of the first type and `rec_j` for the +`j`-th container), and `WellFounded.fix` over the source's measure for +well-founded recursion, a recursive call being the induction hypothesis applied +to the packed arguments and the source's own decrease theorem, which quantifies +the arms enclosing the call, so every numbered `if` and `match` of such a body +is written by cases on its scrutinee with the equation the arm was taken by. +The compiler's implementation record `f._unsafe_rec` of a recursive predicate, +a partial definition beside the safe one, gives the code generator the direct +recursion it has no code for in a recursor; no theorem depends on it. A +definition that a model construct elaborates to pins no axiom: the construct's +declared set bounds it, and the audit finds none for a definition that proves +nothing. Each row pins its axioms exactly, the audit module prints and checks +them, and the record is the authoring notation at the module's path with the +extension `.lex.tex`, which reads back to the data byte for byte. +`tests/preservation-certificates/A-STATEMENTS.md` records that the statement of +every theorem of every certificate A of the four corpora is, up to the kernel's +definitional equality, the statement the text certificate stated. +`conformance_sp_15` builds every certificate A of the four corpora this way, +reads each record back, verifies the production example under the pinned Lean, +and plants a wrong lemma application, a changed statement, a forged program +literal, and an added axiom, each refused. + **Evidence.** The conformance suite certifies every production root of `examples/production`, `examples/production-coverage`, `examples/models`, and `examples/reasoning`, and verification checks every example's certificates as part of its @@ -7951,7 +7989,7 @@ pub fn verify_artifact(bundle: &Utf8Path) -> Result; **Run.** `build` refuses, before any Lean process runs and before anything is published: a malformed request (`LLC0201`); a root that is not a production root (`LLC0202`); an unregistered or undeclared target or another digest (`LLC0203`); a pin the project no longer matches (`LLC0204`); an evidence path that escapes the project or a symlinked bundle area (`LLS8001`); malformed evidence (`LLB6006`, `LLB6007`); evidence whose certificate is not the pinned one, earns a `refused` verdict, or is for a request whose reference program is not the root's lowered program (`LLV7101`); and a package the renderer refuses (`LLB6005`). Every check and eligibility failure of the project fails as it does for `check`. The project is then verified by the unchanged §22 pipeline over the entrypoint selection, so every production root of the project is certified; a project edited during the run is `LLC0204`. For each requested root and target, `build` renders the exporting package `verify` does not assemble (§17.17): the renderer of §17.16 over the verified lowered program, exporting the recorded entry alone as `run` with every parameter owned, with `sources` the semantic ID; its `src/lib.rs` begins with the certified crate's bytes. The bundle is published atomically, content-addressed, at `/production//`; there is no output-directory option. -**Bundle.** `result.json`; `packages///` with `Cargo.toml`, `src/lib.rs`, `provenance.json` (`lexlean/rust-provenance/1`), and `package.json` (the `lexlean/rust-package/1` manifest); and `evidence/`: `attestation.json`, `compiler-input.json`, `preservation.json`, `program/R.json`, `crate/R..rs`, `certificates/.lean` for certificates A and B and `certificates/.lex.tex` for certificate E (its kernel data in the authoring notation, §17.17), and `gnaf/R/request.json` and `certificate.json` where evidence was offered. +**Bundle.** `result.json`; `packages///` with `Cargo.toml`, `src/lib.rs`, `provenance.json` (`lexlean/rust-provenance/1`), and `package.json` (the `lexlean/rust-package/1` manifest); and `evidence/`: `attestation.json`, `compiler-input.json`, `preservation.json`, `program/R.json`, `crate/R..rs`, `certificates/.lean` for certificate B and `certificates/.lex.tex` for certificates A and E (their kernel data in the authoring notation, §17.17), and `gnaf/R/request.json` and `certificate.json` where evidence was offered. **Result.** `lexlean/production-result/2` binds the compiler (its version, the language-1.2 compiler-semantics ID of §21.2, read as an opaque digest, and the Lean toolchain); the source, semantic, and lock identities; the build and attestation IDs and the attestation, compiler input (whose SHA-256 is the compiler-input ID, §22.10), and preservation record; and per root its index `R`, program, and optimality, and per target the target ID and digest, the package's name, version, directory, export, and files, the crate, the entry, the certification, and the effects the output's realization can exhibit on its target, ascending and exactly those that eligibility requires of the root there (§17.13); an output with `recursion` recurses to a depth bounded only by its input, so the host must give the call a stack that depth fits, and exhausting it aborts the process. `lexlean production build` reports how many outputs need such a stack. Every file is stated by bundle-relative path, byte length, and SHA-256. The certification status is the closed `kernel-checked`: certificates A, B, and E compiled silently under the pinned Lean, replayed through `leanchecker`, and their root theorems depend on exactly the stated axioms (§17.17). It is preservation, at level `build`, and never optimality. Optimality is `not-claimed`, or `gnaf` with the certificate identity, the universe identity, the two documents, and the verdict the host checker of §17.18 recomputes, `certified` or `incomplete`; a `refused` verdict is never recorded. `result_id` is SHA-256 over `"lexlean-production-result-v1"` and `frame("result-body", canonical_json(result without result_id))`. The result holds no absolute path, environment value, or time; because it binds the attestation, which records the host and executable hashes (§22.9), it is identical across project locations and working directories on one host and toolchain, not across hosts. diff --git a/crates/conformance/src/cases/preservation.rs b/crates/conformance/src/cases/preservation.rs index 01ee717a7..a06e05199 100644 --- a/crates/conformance/src/cases/preservation.rs +++ b/crates/conformance/src/cases/preservation.rs @@ -3823,6 +3823,59 @@ pub fn run(id: &str) { ); } } + // No Lean text template for certificate A remains in the + // compiler: the sources that build it name no tactic, no + // declaration keyword of a row, and no termination clause. The + // markers are spelled in halves so that this list is not itself + // a template (the same discipline as `audit-deferral`). + let markers: Vec = [ + ("theorem ", "__rel_"), + ("def ", "__fits_"), + ("termination", "_by"), + ("decreasing", "_by"), + ("by ", "simp"), + ("by ", "rw ["), + ("exact ", "_root_"), + ("(fun _ => ", "rfl)"), + ] + .iter() + .map(|(head, tail)| format!("{head}{tail}")) + .collect(); + let templates = |source: &str| -> Vec { + markers + .iter() + .filter(|marker| source.contains(marker.as_str())) + .cloned() + .collect() + }; + let production = repo_root().join("crates/lexlean/src/production"); + let mut scanned = 0usize; + let mut sources = vec![production.join("certificate.rs")]; + for entry in walkdir::WalkDir::new(production.join("certificate/data_a").as_std_path()) + .into_iter() + .flatten() + { + if entry.path().extension().is_some_and(|extension| extension == "rs") { + sources.push(camino::Utf8PathBuf::from_path_buf(entry.path().to_path_buf()) + .expect("a UTF-8 path")); + } + } + sources.push(production.join("certificate/data_a.rs")); + for path in &sources { + let text = std::fs::read_to_string(path.as_std_path()).expect("a source"); + assert!( + templates(&text).is_empty(), + "{path}: Lean text template for certificate A: {:?}", + templates(&text) + ); + scanned += 1; + } + assert!(scanned > 10, "the scan reads the sources that build certificate A"); + // The scan is armed: a planted template is found. + assert!( + !templates(&format!("format!(\"theorem {}0 : …\")", "__rel_")).is_empty(), + "the template scan finds a planted template" + ); if !support::lean_backed("SP-15") { return; } From 43aa048b1f0403a48b32357527bc641a792924d0 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 19:17:38 +0000 Subject: [PATCH 20/41] #89 stage 3: certificate A is clippy-clean under -D warnings Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- crates/lexlean/src/production/certificate.rs | 51 +-- .../src/production/certificate/data_a.rs | 8 +- .../production/certificate/data_a/calls.rs | 130 +++++- .../src/production/certificate/data_a/lean.rs | 12 - .../production/certificate/data_a/matching.rs | 41 +- .../production/certificate/data_a/prims.rs | 16 +- .../production/certificate/data_a/prove.rs | 315 ++++++++++++-- .../src/production/certificate/data_a/rows.rs | 11 +- .../src/production/certificate/data_a/src.rs | 405 +++++++++++++++++- .../certificate/data_a/structural.rs | 125 ++++-- .../src/production/certificate/data_a/wf.rs | 29 +- crates/lexlean/src/production/kernel/ext.rs | 19 +- 12 files changed, 983 insertions(+), 179 deletions(-) diff --git a/crates/lexlean/src/production/certificate.rs b/crates/lexlean/src/production/certificate.rs index 37b6cc186..5fa528de0 100644 --- a/crates/lexlean/src/production/certificate.rs +++ b/crates/lexlean/src/production/certificate.rs @@ -41,10 +41,9 @@ mod data_a; use super::eligibility::BUILTIN_OWNERS; use super::lean_list::{array, list, list_by}; use super::lower::{fixed_kind, Layout, Lowered, Origin, Validation}; -use super::source::{Constructor, Local, Site, Source}; +use super::source::{Local, Site, Source}; use super::RootReport; -use crate::backend::semantic::{hypothesis, identifier as semantic_identifier, string_literal}; -use crate::calculus::library::Template; +use crate::backend::semantic::{identifier as semantic_identifier, string_literal}; use crate::calculus::{ Arm, Expr, Function, IntKind, OrderingValue, Prim, Program, Shape, Ty, Value, }; @@ -72,7 +71,6 @@ pub const LIBRARY_MODULES: [&str; 7] = [ const SYNTAX: &str = "LexLeanTarget.TargetSyntax"; const SEMANTICS: &str = "LexLeanTarget.TargetSemantics"; -const NAT_BOUND: &str = "18446744073709551616"; const SEMANTICS_MODULE: &str = SEMANTICS; /// One generated certificate module. @@ -220,15 +218,8 @@ struct CLocal { /// The local's type as the generic declaration writes it, before the /// instance's type arguments are substituted. generic: SemanticType, - companion: Option, -} - -#[derive(Clone)] -struct Companion { - fits: String, - index: String, - captures: String, - relation: String, + /// Whether the local is function-typed, so carries a companion. + has_companion: bool, } /// What a well-founded body numbers: the hypothesis of each `if` and @@ -301,7 +292,7 @@ impl Ctx { .extend(extra.into_iter().map(|(local, generic)| CLocal { local, generic, - companion: None, + has_companion: false, })); out } @@ -311,11 +302,9 @@ impl Ctx { #[derive(Clone)] enum Recursion { None, - /// Structural recursion on parameter `argument` by top-level equations; - /// a mutual group's label. + /// Structural recursion on parameter `argument` by top-level equations. Structural { argument: String, - mutual: Option, }, /// Well-founded recursion with the source's measure and evidence. WellFounded { @@ -1021,16 +1010,6 @@ pub fn render_program(program: &Program) -> Result { // --- Rendering the source ----------------------------------------------------- -/// A Lean binder for a function-typed local's companion. -fn companion_names(stem: &str) -> Companion { - Companion { - fits: format!("__ff_{stem}"), - index: format!("__fi_{stem}"), - captures: format!("__fc_{stem}"), - relation: format!("__fh_{stem}"), - } -} - /// The parameter and result types of a closed function type. fn function_type(ty: &SemanticType) -> Option<(&[SemanticType], &SemanticType)> { match ty { @@ -1495,17 +1474,14 @@ impl Gen<'_> { } impl Gen<'_> { - /// A scope binding `locals` in order, function-typed ones with companions - /// named by `stem` and position. - fn scope(locals: &[(Local, SemanticType)], stem: &str) -> Vec { + /// A scope binding `locals` in order, function-typed ones with companions. + fn scope(locals: &[(Local, SemanticType)]) -> Vec { locals .iter() - .enumerate() - .map(|(position, (local, generic))| CLocal { + .map(|(local, generic)| CLocal { local: local.clone(), generic: generic.clone(), - companion: function_type(&local.ty) - .map(|_| companion_names(&format!("{stem}{position}"))), + has_companion: function_type(&local.ty).is_some(), }) .collect() } @@ -2010,7 +1986,6 @@ impl<'a> Gen<'a> { Some(argument) => ( Recursion::Structural { argument: argument.clone(), - mutual: mutual.clone(), }, Hypotheses::new(), ), @@ -2020,7 +1995,7 @@ impl<'a> Gen<'a> { let ctx = Ctx::new( site.clone(), instance_parameters.clone(), - Self::scope(&locals, ""), + Self::scope(&locals), hypotheses, index, ); @@ -2333,7 +2308,7 @@ impl<'a> Gen<'a> { type_parameters, }) => { let mut locals = captures.clone(); - locals.extend(Self::scope(parameters, "p")); + locals.extend(Self::scope(parameters)); Ok(Ctx::new( site.clone(), type_parameters.clone(), @@ -2540,7 +2515,7 @@ impl<'a> Gen<'a> { index, Subject::Lambda { site: site.clone(), - captures: Self::scope(&captured, "c"), + captures: Self::scope(&captured), parameters, body, hypotheses: ctx.hypotheses.clone(), diff --git a/crates/lexlean/src/production/certificate/data_a.rs b/crates/lexlean/src/production/certificate/data_a.rs index c6fe9a27d..912bf0198 100644 --- a/crates/lexlean/src/production/certificate/data_a.rs +++ b/crates/lexlean/src/production/certificate/data_a.rs @@ -30,7 +30,7 @@ use crate::ir::core::{ CoreAxiomPolicy, CoreBinderInfo, CoreDeclKind, CoreDeclaration, CoreModule, CoreReducibility, CoreSafety, CoreTransparency, CORE_SPEC_2, }; -use crate::ir::semantic::{MemberRef, SemanticTerm, SemanticType, Step}; +use crate::ir::semantic::{MemberRef, SemanticTerm, Step}; mod boundary; mod calls; @@ -181,7 +181,7 @@ pub(super) struct Writer<'g, 'a> { /// The rendering of each expression of the program, by address. exprs: HashMap<*const Expr, Term>, /// The signature of each function whose relation has been stated. - pub(super) sigs: HashMap, + sigs: HashMap, /// The well-founded definition under derivation. pub(super) wf: Option, } @@ -238,10 +238,6 @@ impl<'g, 'a> Writer<'g, 'a> { // --- Names ------------------------------------------------------------ - pub(super) fn c(&mut self, name: &str) -> Term { - self.b.konst(name) - } - pub(super) fn ts(&mut self, name: &str) -> Term { self.b.konst(&format!("{SYNTAX}.{name}")) } diff --git a/crates/lexlean/src/production/certificate/data_a/calls.rs b/crates/lexlean/src/production/certificate/data_a/calls.rs index 239b98d83..c295818ca 100644 --- a/crates/lexlean/src/production/certificate/data_a/calls.rs +++ b/crates/lexlean/src/production/certificate/data_a/calls.rs @@ -186,7 +186,20 @@ impl<'a> Writer<'_, 'a> { SemanticTerm::Var { name } => { let number = match expr { Expr::Var { name } => *name, - _ => return Err(format!("the lowering of `{name}` is not a variable")), + Expr::Value { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Match { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => { + return Err(format!("the lowering of `{name}` is not a variable")) + } }; let (_, terms) = scope.lookup(name)?; let companion = terms @@ -217,7 +230,20 @@ impl<'a> Writer<'_, 'a> { } => { let (function, capture_exprs) = match expr { Expr::Closure { function, captures } => (*function, captures), - _ => return Err("the lowering of a lambda is not a closure".to_owned()), + Expr::Value { .. } + | Expr::Var { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Match { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => { + return Err("the lowering of a lambda is not a closure".to_owned()) + } }; let index = self.gen.next_lambda(scope.ctx.function)?; if index != function { @@ -252,7 +278,20 @@ impl<'a> Writer<'_, 'a> { encoded.push(self.encoded_local(&local, terms)?); let number = match capture_expr { Expr::Var { name } => *name, - _ => return Err("a capture lowered to other than a variable".to_owned()), + Expr::Value { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Match { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => { + return Err("a capture lowered to other than a variable".to_owned()) + } }; let value = *encoded.last().ok_or("a capture")?; let x = self.nat_u(number); @@ -427,7 +466,20 @@ impl<'a> Writer<'_, 'a> { ) -> Result { let (target_expr, operand_exprs) = match expr { Expr::Apply { target, operands } => (target.as_ref(), operands.as_slice()), - _ => return Err("the lowering of an application is not an apply".to_owned()), + Expr::Value { .. } + | Expr::Var { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Match { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => { + return Err("the lowering of an application is not an apply".to_owned()) + } }; let (p, env) = (self.prog, scope.env); let mut srcs = Vec::new(); @@ -440,7 +492,20 @@ impl<'a> Writer<'_, 'a> { SemanticTerm::Var { name } => { let number = match target_expr { Expr::Var { name } => *name, - _ => return Err(format!("the lowering of `{name}` is not a variable")), + Expr::Value { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Match { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => { + return Err(format!("the lowering of `{name}` is not a variable")) + } }; let (_, terms) = scope.lookup(name)?; let companion = terms @@ -594,7 +659,18 @@ impl<'a> Writer<'_, 'a> { for arg in args { let (_, body) = match self.b.node(ty) { crate::ir::core::CoreNode::Forall { t, v, .. } => (*t, *v), - _ => return Err("a relation hypothesis applied past its binders".to_owned()), + crate::ir::core::CoreNode::BVar { .. } + | crate::ir::core::CoreNode::Sort { .. } + | crate::ir::core::CoreNode::Const { .. } + | crate::ir::core::CoreNode::App { .. } + | crate::ir::core::CoreNode::Lambda { .. } + | crate::ir::core::CoreNode::Let { .. } + | crate::ir::core::CoreNode::Nat { .. } + | crate::ir::core::CoreNode::String { .. } + | crate::ir::core::CoreNode::Mdata { .. } + | crate::ir::core::CoreNode::Projection { .. } => { + return Err("a relation hypothesis applied past its binders".to_owned()) + } }; term = self.b.app(term, *arg); ty = self.b.instantiate_beta(body, *arg); @@ -627,7 +703,47 @@ impl<'a> Writer<'_, 'a> { } Ok(out) } - _ => Ok(Vec::new()), + SemanticTerm::Var { .. } + | SemanticTerm::Nat { .. } + | SemanticTerm::Integer { .. } + | SemanticTerm::String { .. } + | SemanticTerm::Bytes { .. } + | SemanticTerm::Primitive { .. } + | SemanticTerm::Bool { .. } + | SemanticTerm::Unit + | SemanticTerm::Nil { .. } + | SemanticTerm::Cons { .. } + | SemanticTerm::Record { .. } + | SemanticTerm::Constructor { .. } + | SemanticTerm::InstanceValue { .. } + | SemanticTerm::Project { .. } + | SemanticTerm::Call { .. } + | SemanticTerm::If { .. } + | SemanticTerm::Match { .. } + | SemanticTerm::Eq { .. } + | SemanticTerm::Le { .. } + | SemanticTerm::Lt { .. } + | SemanticTerm::Add { .. } + | SemanticTerm::Beq { .. } + | SemanticTerm::Ble { .. } + | SemanticTerm::Blt { .. } + | SemanticTerm::And { .. } + | SemanticTerm::PropAnd { .. } + | SemanticTerm::Or { .. } + | SemanticTerm::Not { .. } + | SemanticTerm::Implies { .. } + | SemanticTerm::Iff { .. } + | SemanticTerm::Forall { .. } + | SemanticTerm::Let { .. } + | SemanticTerm::Pair { .. } + | SemanticTerm::First { .. } + | SemanticTerm::Second { .. } + | SemanticTerm::Apply { .. } + | SemanticTerm::FunctionRef { .. } + | SemanticTerm::MapLiteral { .. } + | SemanticTerm::SetLiteral { .. } + | SemanticTerm::GraphLiteral { .. } + | SemanticTerm::CheckedApply { .. } => Ok(Vec::new()), } } } diff --git a/crates/lexlean/src/production/certificate/data_a/lean.rs b/crates/lexlean/src/production/certificate/data_a/lean.rs index e59278ec3..b0e099a39 100644 --- a/crates/lexlean/src/production/certificate/data_a/lean.rs +++ b/crates/lexlean/src/production/certificate/data_a/lean.rs @@ -2,7 +2,6 @@ //! target program's literals, and the source's denotation (`src`), each the //! term the pinned Lean elaborates the text of §17.17 to. -use super::super::NAT_BOUND; use super::Writer; use crate::calculus::{ Arm, Expr, Function, IntKind, OrderingValue, Prim, Program, Shape, Ty, Value, @@ -95,17 +94,6 @@ impl Writer<'_, '_> { self.b.apps(blt, &[a, b]) } - /// The bound of a 64-bit natural, as the width predicates state it. - pub(super) fn nat_bound(&mut self) -> Term { - self.nat_lit(NAT_BOUND) - } - - /// `Nat.blt n 2^64`. - pub(super) fn below_bound(&mut self, n: Term) -> Term { - let bound = self.nat_bound(); - self.nat_blt(n, bound) - } - // --- Types ------------------------------------------------------------ /// The Lean type of a closed source type. diff --git a/crates/lexlean/src/production/certificate/data_a/matching.rs b/crates/lexlean/src/production/certificate/data_a/matching.rs index 546b063e7..27cc87c24 100644 --- a/crates/lexlean/src/production/certificate/data_a/matching.rs +++ b/crates/lexlean/src/production/certificate/data_a/matching.rs @@ -142,7 +142,18 @@ impl<'a> Writer<'_, 'a> { scrutinee, arms, } => (ty, scrutinee.as_ref(), arms.as_slice()), - _ => return Err("the lowering of a match is not a match".to_owned()), + Expr::Value { .. } + | Expr::Var { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => return Err("the lowering of a match is not a match".to_owned()), }; let scrutinee_ty = self.infer(scrutinee, scope)?; let result_ty = self.infer(whole, scope)?; @@ -385,7 +396,20 @@ impl<'a> Writer<'_, 'a> { scrutinee, arms, } => (ty, scrutinee.as_ref(), arms.as_slice()), - _ => return Err("a violation match's pair arm is not a match".to_owned()), + Expr::Value { .. } + | Expr::Var { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => { + return Err("a violation match's pair arm is not a match".to_owned()) + } }; let _ = first_expr; let bool = self.b.konst("Bool"); @@ -456,7 +480,18 @@ impl<'a> Writer<'_, 'a> { .ok_or("a violation match lowered to fewer halves")?; let (second_ty, leaf_arms) = match &second_arms.body { Expr::Match { ty, arms, .. } => (ty, arms.as_slice()), - _ => return Err("a violation half is not a match".to_owned()), + Expr::Value { .. } + | Expr::Var { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => return Err("a violation half is not a match".to_owned()), }; let leaf = leaf_arms .get(usize::from(second_value)) diff --git a/crates/lexlean/src/production/certificate/data_a/prims.rs b/crates/lexlean/src/production/certificate/data_a/prims.rs index accc0550e..b6a1522b4 100644 --- a/crates/lexlean/src/production/certificate/data_a/prims.rs +++ b/crates/lexlean/src/production/certificate/data_a/prims.rs @@ -342,7 +342,21 @@ impl<'a> Writer<'_, 'a> { let _ = whole; let operand_exprs = match expr { Expr::Prim { operands, .. } if operands.len() == arguments.len() => operands, - _ => return Err("the lowering of a primitive is not a primitive".to_owned()), + Expr::Value { .. } + | Expr::Var { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Match { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => { + return Err("the lowering of a primitive is not a primitive".to_owned()) + } }; let rule = self.prim_rule(operation, &types, &result, &site)?; let mut proofs = Vec::new(); diff --git a/crates/lexlean/src/production/certificate/data_a/prove.rs b/crates/lexlean/src/production/certificate/data_a/prove.rs index 81f8289d9..58238adad 100644 --- a/crates/lexlean/src/production/certificate/data_a/prove.rs +++ b/crates/lexlean/src/production/certificate/data_a/prove.rs @@ -2,16 +2,13 @@ //! of the preservation library per construct, applied at the program //! expression the lowering produced for it. -use super::super::{element_of, Ctx}; -use super::{CompTerms, LocalTerm, Pf, Scope, Writer, EXPLICIT}; -use crate::calculus::{Arm, Expr, Shape, Ty}; +use super::super::element_of; +use super::{LocalTerm, Pf, Scope, Writer, EXPLICIT}; +use crate::calculus::{Expr, Shape}; use crate::ir::core::CoreLevel; -use crate::ir::semantic::{ - SemanticBranch, SemanticEdge, SemanticMapEntry, SemanticPrimitive, SemanticTerm, SemanticType, - Step, -}; +use crate::ir::semantic::{SemanticEdge, SemanticMapEntry, SemanticTerm, SemanticType, Step}; use crate::production::kernel::{Given, Term}; -use crate::production::source::{Constructor, Local, Source}; +use crate::production::source::{Constructor, Source}; /// What the proof of an argument contributes to its callee's relation. pub(super) struct Passed { @@ -42,14 +39,6 @@ impl<'a> Writer<'_, 'a> { self.b.apps(rel, &[fits, value]) } - /// `Conv p env e (Rel fits value)`. - pub(super) fn conv_ty(&mut self, env: Term, e: Term, fits: Term, value: Term) -> Term { - let conv = self.lp("Conv"); - let rel = self.rel(fits, value); - let p = self.prog; - self.b.apps(conv, &[p, env, e, rel]) - } - /// The width predicate and value a derivation's type relates. A type /// under `let` bindings relates the bindings applied to each. pub(super) fn rel_parts(&mut self, ty: Term) -> Result<(Term, Term), String> { @@ -119,7 +108,20 @@ impl<'a> Writer<'_, 'a> { ty, operands, } => (*shape, ty, operands), - _ => return Err("the lowering of a construction is not a build".to_owned()), + Expr::Value { .. } + | Expr::Var { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Match { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => { + return Err("the lowering of a construction is not a build".to_owned()) + } }; if exprs.len() != operands.len() { return Err(format!( @@ -184,7 +186,20 @@ impl<'a> Writer<'_, 'a> { SemanticTerm::Var { name } => { let number = match expr { Expr::Var { name } => *name, - _ => return Err(format!("the lowering of `{name}` is not a variable")), + Expr::Value { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Match { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => { + return Err(format!("the lowering of `{name}` is not a variable")) + } }; let (local, terms) = scope.lookup(name)?; let local = local.clone(); @@ -205,7 +220,20 @@ impl<'a> Writer<'_, 'a> { | SemanticTerm::Bytes { hex: _ } => { let (ty, value) = match expr { Expr::Value { ty, value } => (ty, value), - _ => return Err("the lowering of a literal is not a value".to_owned()), + Expr::Var { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Match { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => { + return Err("the lowering of a literal is not a value".to_owned()) + } }; let ty = self.ty_term(ty); let value = self.value_term(value)?; @@ -241,7 +269,21 @@ impl<'a> Writer<'_, 'a> { Expr::Build { operands, .. } if operands.len() == 2 => { (&operands[0], &operands[1]) } - _ => return Err("the lowering of a cons is not a build of two".to_owned()), + Expr::Value { .. } + | Expr::Var { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Match { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => { + return Err("the lowering of a cons is not a build of two".to_owned()) + } }; let ty = self.infer(term, scope)?; let head_pf = self.prove(head, head_expr, scope)?; @@ -343,7 +385,20 @@ impl<'a> Writer<'_, 'a> { let index = self.gen.instance_index(&module, &resolved.name, &[])?; let function = match expr { Expr::Call { function, .. } => *function, - _ => return Err("the lowering of an instance is not a call".to_owned()), + Expr::Value { .. } + | Expr::Var { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Match { .. } + | Expr::Build { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => { + return Err("the lowering of an instance is not a call".to_owned()) + } }; if function != index { return Err(format!( @@ -364,7 +419,20 @@ impl<'a> Writer<'_, 'a> { SemanticTerm::Project { value, field } => { let (inner_expr, index) = match expr { Expr::Field { value, index } => (value.as_ref(), *index), - _ => return Err("the lowering of a projection is not a field".to_owned()), + Expr::Value { .. } + | Expr::Var { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Match { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } => { + return Err("the lowering of a projection is not a field".to_owned()) + } }; let ty = self.infer(value, scope)?; let shape = self.gen.source.document(&ty)?; @@ -415,7 +483,20 @@ impl<'a> Writer<'_, 'a> { let index = self.gen.instance_index(&module, &function.name, &closed)?; let (callee, operand_exprs) = match expr { Expr::Call { function, operands } => (*function, operands), - _ => return Err("the lowering of a call is not a call".to_owned()), + Expr::Value { .. } + | Expr::Var { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Match { .. } + | Expr::Build { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => { + return Err("the lowering of a call is not a call".to_owned()) + } }; if callee != index { return Err(format!("a call of function {index} lowered to {callee}")); @@ -460,7 +541,20 @@ impl<'a> Writer<'_, 'a> { then_branch.as_ref(), else_branch.as_ref(), ), - _ => return Err("the lowering of a conditional is not a cond".to_owned()), + Expr::Value { .. } + | Expr::Var { .. } + | Expr::Let { .. } + | Expr::Match { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => { + return Err("the lowering of a conditional is not a cond".to_owned()) + } }; self.conditional(term, condition, then_value, else_value, (c, t, e), scope) } @@ -499,7 +593,20 @@ impl<'a> Writer<'_, 'a> { then_branch.as_ref(), else_branch.as_ref(), ), - _ => return Err("the lowering of && is not a cond".to_owned()), + Expr::Value { .. } + | Expr::Var { .. } + | Expr::Let { .. } + | Expr::Match { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => { + return Err("the lowering of && is not a cond".to_owned()) + } }; let else_term = SemanticTerm::Bool { value: false }; self.conditional_of( @@ -523,7 +630,20 @@ impl<'a> Writer<'_, 'a> { then_branch.as_ref(), else_branch.as_ref(), ), - _ => return Err("the lowering of || is not a cond".to_owned()), + Expr::Value { .. } + | Expr::Var { .. } + | Expr::Let { .. } + | Expr::Match { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => { + return Err("the lowering of || is not a cond".to_owned()) + } }; let then_term = SemanticTerm::Bool { value: true }; self.conditional_of( @@ -539,7 +659,20 @@ impl<'a> Writer<'_, 'a> { SemanticTerm::Not { value } => { let operand_exprs = match expr { Expr::Prim { operands, .. } => operands, - _ => return Err("the lowering of ! is not a primitive".to_owned()), + Expr::Value { .. } + | Expr::Var { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Match { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => { + return Err("the lowering of ! is not a primitive".to_owned()) + } }; let inner = self.prove(value, &operand_exprs[0], scope)?; let src = self.src(value, scope)?; @@ -571,14 +704,52 @@ impl<'a> Writer<'_, 'a> { SemanticTerm::First { value } | SemanticTerm::Second { value } => { let inner_expr = match expr { Expr::First { value } | Expr::Second { value } => value.as_ref(), - _ => { + Expr::Value { .. } + | Expr::Var { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Match { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::Field { .. } => { return Err("the lowering of a projection is not first or second".to_owned()) } }; let ty = self.infer(value, scope)?; let (left, right) = match &ty { SemanticType::Product { left, right } => (left.as_ref(), right.as_ref()), - _ => return Err("a projection of a non-product".to_owned()), + SemanticType::Type + | SemanticType::Parameter { .. } + | SemanticType::Nat + | SemanticType::Bool + | SemanticType::Prop + | SemanticType::Unit + | SemanticType::Int + | SemanticType::Int8 + | SemanticType::Int16 + | SemanticType::Int32 + | SemanticType::Int64 + | SemanticType::UInt8 + | SemanticType::UInt16 + | SemanticType::UInt32 + | SemanticType::UInt64 + | SemanticType::String + | SemanticType::Bytes + | SemanticType::Ordering + | SemanticType::Option { .. } + | SemanticType::Result { .. } + | SemanticType::List { .. } + | SemanticType::Named { .. } + | SemanticType::Function { .. } + | SemanticType::Map { .. } + | SemanticType::Set { .. } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure => { + return Err("a projection of a non-product".to_owned()) + } }; let inner = self.prove(value, inner_expr, scope)?; // The pair's encoding unfolds to the pair of its components'. @@ -739,7 +910,18 @@ impl<'a> Writer<'_, 'a> { fn operand_exprs(&self, expr: &'a Expr) -> Result<&'a [Expr], String> { match expr { Expr::Build { operands, .. } => Ok(operands), - _ => Err("the lowering of a construction is not a build".to_owned()), + Expr::Value { .. } + | Expr::Var { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Match { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => Err("the lowering of a construction is not a build".to_owned()), } } @@ -751,7 +933,19 @@ impl<'a> Writer<'_, 'a> { operands, .. } if operands.len() == 2 => Ok((&operands[0], &operands[1])), - _ => { + Expr::Value { .. } + | Expr::Var { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Match { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => { Err("a literal collection lowered to something other than a cons chain".to_owned()) } } @@ -838,7 +1032,35 @@ impl<'a> Writer<'_, 'a> { ) -> Result { let (member, arguments) = match ty { SemanticType::Named { member, arguments } => (member, arguments), - _ => return Err("a projection from a non-document".to_owned()), + SemanticType::Type + | SemanticType::Parameter { .. } + | SemanticType::Nat + | SemanticType::Bool + | SemanticType::Prop + | SemanticType::Unit + | SemanticType::Int + | SemanticType::Int8 + | SemanticType::Int16 + | SemanticType::Int32 + | SemanticType::Int64 + | SemanticType::UInt8 + | SemanticType::UInt16 + | SemanticType::UInt32 + | SemanticType::UInt64 + | SemanticType::String + | SemanticType::Bytes + | SemanticType::Ordering + | SemanticType::Option { .. } + | SemanticType::Result { .. } + | SemanticType::List { .. } + | SemanticType::Product { .. } + | SemanticType::Function { .. } + | SemanticType::Map { .. } + | SemanticType::Set { .. } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure => { + return Err("a projection from a non-document".to_owned()) + } }; let owner = self.document_name(member)?; let head = self.b.konst(&format!("{owner}.{field}")); @@ -861,7 +1083,21 @@ impl<'a> Writer<'_, 'a> { ) -> Result { let operand_exprs = match expr { Expr::Prim { operands, .. } if operands.len() == 2 => operands, - _ => return Err("the lowering of an arithmetic term is not a primitive".to_owned()), + Expr::Value { .. } + | Expr::Var { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Match { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => { + return Err("the lowering of an arithmetic term is not a primitive".to_owned()) + } }; let l = self.prove(left, &operand_exprs[0], scope)?; let r = self.prove(right, &operand_exprs[1], scope)?; @@ -1069,7 +1305,18 @@ impl<'a> Writer<'_, 'a> { bound, body, } => (*name, ty, bound.as_ref(), body.as_ref()), - _ => return Err("the lowering of a let is not a let".to_owned()), + Expr::Value { .. } + | Expr::Var { .. } + | Expr::Cond { .. } + | Expr::Match { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => return Err("the lowering of a let is not a let".to_owned()), }; let (local, generic) = self.gen.let_local(binder, &scope.ctx); let local_ty = local.ty.clone(); diff --git a/crates/lexlean/src/production/certificate/data_a/rows.rs b/crates/lexlean/src/production/certificate/data_a/rows.rs index 7ba35da14..2b286cb57 100644 --- a/crates/lexlean/src/production/certificate/data_a/rows.rs +++ b/crates/lexlean/src/production/certificate/data_a/rows.rs @@ -6,7 +6,7 @@ use std::collections::BTreeMap; use super::super::{function_type, identifier, CLocal, Ctx, Hypotheses, Recursion, Subject}; use super::{CompTerms, LocalTerm, Pf, RowKind, Scope, Writer, EXPLICIT}; use crate::calculus::Expr; -use crate::ir::semantic::{MemberRef, SemanticParameter, SemanticTerm, SemanticType}; +use crate::ir::semantic::{MemberRef, SemanticParameter, SemanticType}; use crate::production::kernel::{Given, Term}; use crate::production::source::{Local, Site}; @@ -355,7 +355,7 @@ impl<'a> Writer<'_, 'a> { site, type_parameters, } => { - let clocals = Gen::scope(&locals, ""); + let clocals = Gen::scope(&locals); match recursion { Recursion::None => { if recursive { @@ -543,7 +543,7 @@ impl<'a> Writer<'_, 'a> { ) }) .collect(); - let clocals = Gen::scope(&locals, ""); + let clocals = Gen::scope(&locals); let set = self.local_set(0, &clocals)?; let head = self.b.konst(&root_lean); let values: Vec = set.terms.iter().map(|terms| terms.value).collect(); @@ -597,11 +597,6 @@ impl<'a> Writer<'_, 'a> { }) } - /// Subject lookups for tools that read a function's definition. - pub(super) fn is_term_var(term: &SemanticTerm) -> bool { - matches!(term, SemanticTerm::Var { name: _ }) - } - /// A record (instance or document value) of closed type `ty`. pub(super) fn record( &mut self, diff --git a/crates/lexlean/src/production/certificate/data_a/src.rs b/crates/lexlean/src/production/certificate/data_a/src.rs index 8665c53fa..2d13ef27a 100644 --- a/crates/lexlean/src/production/certificate/data_a/src.rs +++ b/crates/lexlean/src/production/certificate/data_a/src.rs @@ -76,11 +76,6 @@ impl Writer<'_, '_> { Ok(self.b.konst(&format!("{module}.{namespace}.{name}"))) } - fn runtime_name(&self, site: &Site, namespace: &str, name: &str) -> Result { - let module = self.gen.source.lean_module(&site.module)?; - Ok(format!("{module}.{namespace}.{name}")) - } - // --- Instances -------------------------------------------------------- fn fixed_name(ty: &SemanticType) -> Result<&'static str, String> { @@ -130,10 +125,36 @@ impl Writer<'_, '_> { } /// The `ToMathInt` instance of an operand of `checkedConvert`. - fn to_math_int(&mut self, site: &Site, ty: &SemanticType) -> Result { + fn math_int_instance(&mut self, site: &Site, ty: &SemanticType) -> Result { match ty { SemanticType::Int => self.runtime(site, "LexLeanRuntime", "instToMathIntInt"), - _ => { + SemanticType::Type + | SemanticType::Parameter { .. } + | SemanticType::Nat + | SemanticType::Bool + | SemanticType::Prop + | SemanticType::Unit + | SemanticType::Int8 + | SemanticType::Int16 + | SemanticType::Int32 + | SemanticType::Int64 + | SemanticType::UInt8 + | SemanticType::UInt16 + | SemanticType::UInt32 + | SemanticType::UInt64 + | SemanticType::String + | SemanticType::Bytes + | SemanticType::Ordering + | SemanticType::Option { .. } + | SemanticType::Result { .. } + | SemanticType::List { .. } + | SemanticType::Named { .. } + | SemanticType::Product { .. } + | SemanticType::Function { .. } + | SemanticType::Map { .. } + | SemanticType::Set { .. } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure => { let name = Self::fixed_name(ty)?; let to = self.runtime(site, "LexLeanRuntime", "Fixed.toToMathInt")?; let fixed = self.lean_ty(ty)?; @@ -225,7 +246,55 @@ impl Writer<'_, '_> { SemanticPrimitive::CheckedMultiply => Some("checkedMultiplyInt64"), SemanticPrimitive::CheckedNegate => Some("checkedNegateInt64"), SemanticPrimitive::CheckedQuotient => Some("checkedQuotientInt64"), - _ => None, + SemanticPrimitive::Subtract + | SemanticPrimitive::Multiply + | SemanticPrimitive::Quotient + | SemanticPrimitive::Remainder + | SemanticPrimitive::Negate + | SemanticPrimitive::CheckedConvert + | SemanticPrimitive::BitAnd + | SemanticPrimitive::BitOr + | SemanticPrimitive::BitXor + | SemanticPrimitive::BitNot + | SemanticPrimitive::ShiftLeft + | SemanticPrimitive::ShiftRight + | SemanticPrimitive::Append + | SemanticPrimitive::Length + | SemanticPrimitive::Index + | SemanticPrimitive::Slice + | SemanticPrimitive::Utf8Encode + | SemanticPrimitive::Utf8Decode + | SemanticPrimitive::CompareBytes + | SemanticPrimitive::Equal + | SemanticPrimitive::SplitExact + | SemanticPrimitive::Join + | SemanticPrimitive::ParseDecimal + | SemanticPrimitive::FormatDecimal + | SemanticPrimitive::MapInsert + | SemanticPrimitive::MapRemove + | SemanticPrimitive::MapLookup + | SemanticPrimitive::MapContains + | SemanticPrimitive::MapSize + | SemanticPrimitive::MapKeys + | SemanticPrimitive::MapValues + | SemanticPrimitive::MapEntries + | SemanticPrimitive::MapFold + | SemanticPrimitive::SetInsert + | SemanticPrimitive::SetRemove + | SemanticPrimitive::SetContains + | SemanticPrimitive::SetSize + | SemanticPrimitive::SetElements + | SemanticPrimitive::SetUnion + | SemanticPrimitive::SetIntersection + | SemanticPrimitive::SetDifference + | SemanticPrimitive::SetFold + | SemanticPrimitive::ListFold + | SemanticPrimitive::Iterate + | SemanticPrimitive::IterateUntil + | SemanticPrimitive::GraphSuccessors + | SemanticPrimitive::GraphReachable + | SemanticPrimitive::GraphTopological + | SemanticPrimitive::LessThan => None, }; if let Some(special) = special { if *result == int64 { @@ -255,11 +324,39 @@ impl Writer<'_, '_> { let from = first.ok_or("a conversion without an operand")?; let to = match result { SemanticType::Option { value } => value.as_ref(), - _ => return Err("a conversion to a non-option".to_owned()), + SemanticType::Type + | SemanticType::Parameter { .. } + | SemanticType::Nat + | SemanticType::Bool + | SemanticType::Prop + | SemanticType::Unit + | SemanticType::Int + | SemanticType::Int8 + | SemanticType::Int16 + | SemanticType::Int32 + | SemanticType::Int64 + | SemanticType::UInt8 + | SemanticType::UInt16 + | SemanticType::UInt32 + | SemanticType::UInt64 + | SemanticType::String + | SemanticType::Bytes + | SemanticType::Ordering + | SemanticType::Result { .. } + | SemanticType::List { .. } + | SemanticType::Named { .. } + | SemanticType::Product { .. } + | SemanticType::Function { .. } + | SemanticType::Map { .. } + | SemanticType::Set { .. } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure => { + return Err("a conversion to a non-option".to_owned()) + } }; leading.push(self.lean_ty(from)?); leading.push(self.lean_ty(to)?); - leading.push(self.to_math_int(site, from)?); + leading.push(self.math_int_instance(site, from)?); leading.push(self.fixed_instance(site, to)?); } SemanticPrimitive::Subtract @@ -292,7 +389,58 @@ impl Writer<'_, '_> { let class = match operation { SemanticPrimitive::Append => "Appendable", SemanticPrimitive::Length => "Lengthable", - _ => "Sliceable", + SemanticPrimitive::Subtract + | SemanticPrimitive::Multiply + | SemanticPrimitive::Quotient + | SemanticPrimitive::Remainder + | SemanticPrimitive::Negate + | SemanticPrimitive::CheckedConvert + | SemanticPrimitive::CheckedAdd + | SemanticPrimitive::CheckedSubtract + | SemanticPrimitive::CheckedMultiply + | SemanticPrimitive::CheckedNegate + | SemanticPrimitive::CheckedQuotient + | SemanticPrimitive::BitAnd + | SemanticPrimitive::BitOr + | SemanticPrimitive::BitXor + | SemanticPrimitive::BitNot + | SemanticPrimitive::ShiftLeft + | SemanticPrimitive::ShiftRight + | SemanticPrimitive::Index + | SemanticPrimitive::Slice + | SemanticPrimitive::Utf8Encode + | SemanticPrimitive::Utf8Decode + | SemanticPrimitive::CompareBytes + | SemanticPrimitive::Equal + | SemanticPrimitive::SplitExact + | SemanticPrimitive::Join + | SemanticPrimitive::ParseDecimal + | SemanticPrimitive::FormatDecimal + | SemanticPrimitive::MapInsert + | SemanticPrimitive::MapRemove + | SemanticPrimitive::MapLookup + | SemanticPrimitive::MapContains + | SemanticPrimitive::MapSize + | SemanticPrimitive::MapKeys + | SemanticPrimitive::MapValues + | SemanticPrimitive::MapEntries + | SemanticPrimitive::MapFold + | SemanticPrimitive::SetInsert + | SemanticPrimitive::SetRemove + | SemanticPrimitive::SetContains + | SemanticPrimitive::SetSize + | SemanticPrimitive::SetElements + | SemanticPrimitive::SetUnion + | SemanticPrimitive::SetIntersection + | SemanticPrimitive::SetDifference + | SemanticPrimitive::SetFold + | SemanticPrimitive::ListFold + | SemanticPrimitive::Iterate + | SemanticPrimitive::IterateUntil + | SemanticPrimitive::GraphSuccessors + | SemanticPrimitive::GraphReachable + | SemanticPrimitive::GraphTopological + | SemanticPrimitive::LessThan => "Sliceable", }; leading.push(self.sequence_instance(site, class, ty)?); } @@ -308,7 +456,33 @@ impl Writer<'_, '_> { "instIndexableByteArrayUInt8", )?); } - _ => { + SemanticType::Type + | SemanticType::Parameter { .. } + | SemanticType::Nat + | SemanticType::Bool + | SemanticType::Prop + | SemanticType::Unit + | SemanticType::Int + | SemanticType::Int8 + | SemanticType::Int16 + | SemanticType::Int32 + | SemanticType::Int64 + | SemanticType::UInt8 + | SemanticType::UInt16 + | SemanticType::UInt32 + | SemanticType::UInt64 + | SemanticType::String + | SemanticType::Ordering + | SemanticType::Option { .. } + | SemanticType::Result { .. } + | SemanticType::List { .. } + | SemanticType::Named { .. } + | SemanticType::Product { .. } + | SemanticType::Function { .. } + | SemanticType::Map { .. } + | SemanticType::Set { .. } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure => { let element = self.lean_ty(&element_of(ty)?)?; leading.push(element); let head = self.runtime(site, "LexLeanRuntime", "instIndexableList")?; @@ -324,7 +498,35 @@ impl Writer<'_, '_> { SemanticPrimitive::ParseDecimal => { let target = match result { SemanticType::Option { value } => value.as_ref(), - _ => return Err("a decimal parse to a non-option".to_owned()), + SemanticType::Type + | SemanticType::Parameter { .. } + | SemanticType::Nat + | SemanticType::Bool + | SemanticType::Prop + | SemanticType::Unit + | SemanticType::Int + | SemanticType::Int8 + | SemanticType::Int16 + | SemanticType::Int32 + | SemanticType::Int64 + | SemanticType::UInt8 + | SemanticType::UInt16 + | SemanticType::UInt32 + | SemanticType::UInt64 + | SemanticType::String + | SemanticType::Bytes + | SemanticType::Ordering + | SemanticType::Result { .. } + | SemanticType::List { .. } + | SemanticType::Named { .. } + | SemanticType::Product { .. } + | SemanticType::Function { .. } + | SemanticType::Map { .. } + | SemanticType::Set { .. } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure => { + return Err("a decimal parse to a non-option".to_owned()) + } }; leading.push(self.lean_ty(target)?); leading.push(self.decimal_instance(site, target)?); @@ -386,7 +588,32 @@ impl Writer<'_, '_> { SemanticType::String => { self.runtime(site, "LexLeanRuntime", &format!("inst{class}String")) } - _ => { + SemanticType::Type + | SemanticType::Parameter { .. } + | SemanticType::Nat + | SemanticType::Bool + | SemanticType::Prop + | SemanticType::Unit + | SemanticType::Int + | SemanticType::Int8 + | SemanticType::Int16 + | SemanticType::Int32 + | SemanticType::Int64 + | SemanticType::UInt8 + | SemanticType::UInt16 + | SemanticType::UInt32 + | SemanticType::UInt64 + | SemanticType::Ordering + | SemanticType::Option { .. } + | SemanticType::Result { .. } + | SemanticType::List { .. } + | SemanticType::Named { .. } + | SemanticType::Product { .. } + | SemanticType::Function { .. } + | SemanticType::Map { .. } + | SemanticType::Set { .. } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure => { let element = self.lean_ty(&element_of(ty)?)?; let head = self.runtime(site, "LexLeanRuntime", &format!("inst{class}List"))?; Ok(self.b.app(head, element)) @@ -441,7 +668,35 @@ impl Writer<'_, '_> { Constructor::Ok | Constructor::Error => { let (ok, error) = match ty { SemanticType::Result { ok, error } => (ok.as_ref(), error.as_ref()), - _ => return Err("a result constructor at a non-result type".to_owned()), + SemanticType::Type + | SemanticType::Parameter { .. } + | SemanticType::Nat + | SemanticType::Bool + | SemanticType::Prop + | SemanticType::Unit + | SemanticType::Int + | SemanticType::Int8 + | SemanticType::Int16 + | SemanticType::Int32 + | SemanticType::Int64 + | SemanticType::UInt8 + | SemanticType::UInt16 + | SemanticType::UInt32 + | SemanticType::UInt64 + | SemanticType::String + | SemanticType::Bytes + | SemanticType::Ordering + | SemanticType::Option { .. } + | SemanticType::List { .. } + | SemanticType::Named { .. } + | SemanticType::Product { .. } + | SemanticType::Function { .. } + | SemanticType::Map { .. } + | SemanticType::Set { .. } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure => { + return Err("a result constructor at a non-result type".to_owned()) + } }; let ok = self.lean_ty(ok)?; let error = self.lean_ty(error)?; @@ -708,7 +963,35 @@ impl Writer<'_, '_> { } (self.document_name(member)?, params) } - _ => return Err("a projection from a non-document".to_owned()), + SemanticType::Type + | SemanticType::Parameter { .. } + | SemanticType::Nat + | SemanticType::Bool + | SemanticType::Prop + | SemanticType::Unit + | SemanticType::Int + | SemanticType::Int8 + | SemanticType::Int16 + | SemanticType::Int32 + | SemanticType::Int64 + | SemanticType::UInt8 + | SemanticType::UInt16 + | SemanticType::UInt32 + | SemanticType::UInt64 + | SemanticType::String + | SemanticType::Bytes + | SemanticType::Ordering + | SemanticType::Option { .. } + | SemanticType::Result { .. } + | SemanticType::List { .. } + | SemanticType::Product { .. } + | SemanticType::Function { .. } + | SemanticType::Map { .. } + | SemanticType::Set { .. } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure => { + return Err("a projection from a non-document".to_owned()) + } }; let head = self.b.konst(&format!("{}.{field}", owner.0)); let value = self.src(value, scope)?; @@ -826,7 +1109,35 @@ impl Writer<'_, '_> { let ty = self.infer(term, scope)?; let (alpha, beta) = match &ty { SemanticType::Product { left, right } => (left.as_ref(), right.as_ref()), - _ => return Err("a pair of a non-product type".to_owned()), + SemanticType::Type + | SemanticType::Parameter { .. } + | SemanticType::Nat + | SemanticType::Bool + | SemanticType::Prop + | SemanticType::Unit + | SemanticType::Int + | SemanticType::Int8 + | SemanticType::Int16 + | SemanticType::Int32 + | SemanticType::Int64 + | SemanticType::UInt8 + | SemanticType::UInt16 + | SemanticType::UInt32 + | SemanticType::UInt64 + | SemanticType::String + | SemanticType::Bytes + | SemanticType::Ordering + | SemanticType::Option { .. } + | SemanticType::Result { .. } + | SemanticType::List { .. } + | SemanticType::Named { .. } + | SemanticType::Function { .. } + | SemanticType::Map { .. } + | SemanticType::Set { .. } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure => { + return Err("a pair of a non-product type".to_owned()) + } }; let alpha = self.lean_ty(alpha)?; let beta = self.lean_ty(beta)?; @@ -838,7 +1149,35 @@ impl Writer<'_, '_> { let ty = self.infer(value, scope)?; let (alpha, beta) = match &ty { SemanticType::Product { left, right } => (left.as_ref(), right.as_ref()), - _ => return Err("a projection of a non-product".to_owned()), + SemanticType::Type + | SemanticType::Parameter { .. } + | SemanticType::Nat + | SemanticType::Bool + | SemanticType::Prop + | SemanticType::Unit + | SemanticType::Int + | SemanticType::Int8 + | SemanticType::Int16 + | SemanticType::Int32 + | SemanticType::Int64 + | SemanticType::UInt8 + | SemanticType::UInt16 + | SemanticType::UInt32 + | SemanticType::UInt64 + | SemanticType::String + | SemanticType::Bytes + | SemanticType::Ordering + | SemanticType::Option { .. } + | SemanticType::Result { .. } + | SemanticType::List { .. } + | SemanticType::Named { .. } + | SemanticType::Function { .. } + | SemanticType::Map { .. } + | SemanticType::Set { .. } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure => { + return Err("a projection of a non-product".to_owned()) + } }; let alpha = self.lean_ty(alpha)?; let beta = self.lean_ty(beta)?; @@ -986,7 +1325,35 @@ impl Writer<'_, '_> { let shape = self.gen.source.document(ty)?; let (member, arguments) = match ty { SemanticType::Named { member, arguments } => (member, arguments), - _ => return Err("a record of a non-document type".to_owned()), + SemanticType::Type + | SemanticType::Parameter { .. } + | SemanticType::Nat + | SemanticType::Bool + | SemanticType::Prop + | SemanticType::Unit + | SemanticType::Int + | SemanticType::Int8 + | SemanticType::Int16 + | SemanticType::Int32 + | SemanticType::Int64 + | SemanticType::UInt8 + | SemanticType::UInt16 + | SemanticType::UInt32 + | SemanticType::UInt64 + | SemanticType::String + | SemanticType::Bytes + | SemanticType::Ordering + | SemanticType::Option { .. } + | SemanticType::Result { .. } + | SemanticType::List { .. } + | SemanticType::Product { .. } + | SemanticType::Function { .. } + | SemanticType::Map { .. } + | SemanticType::Set { .. } + | SemanticType::ContractViolation + | SemanticType::ReasoningFailure => { + return Err("a record of a non-document type".to_owned()) + } }; let owner = self.document_name(member)?; let mut all = Vec::new(); diff --git a/crates/lexlean/src/production/certificate/data_a/structural.rs b/crates/lexlean/src/production/certificate/data_a/structural.rs index 9e2255693..2a8d4a14e 100644 --- a/crates/lexlean/src/production/certificate/data_a/structural.rs +++ b/crates/lexlean/src/production/certificate/data_a/structural.rs @@ -8,20 +8,22 @@ use std::collections::{BTreeMap, HashMap}; use super::super::{CLocal, Ctx, Gen, Hypotheses, Recursion, Subject}; use super::recplan::RecPlan; use super::rows::{Binder, LocalSet, Sig}; -use super::{LocalTerm, Pf, RowKind, Scope, Writer, EXPLICIT}; +use super::{LocalTerm, RowKind, Scope, Writer, EXPLICIT}; use crate::calculus::Expr; use crate::ir::core::CoreLevel; use crate::ir::semantic::{SemanticTerm, SemanticType}; use crate::production::kernel::{Given, Term}; use crate::production::source::{Local, Site}; +/// The minor premises of one constructor: the width-predicate minor, the +/// relation minor, and the direct form of each function's, by member slot. +type StructuralMinors = (Term, Term, Vec<(usize, Term)>); + /// One function of a structurally recursive group. struct Member<'a> { index: u64, module: String, name: String, - type_arguments: Vec, - result: SemanticType, body: &'a SemanticTerm, site: Site, type_parameters: Vec<(String, SemanticType)>, @@ -35,7 +37,6 @@ struct Member<'a> { /// and the relation's. fits_arg: usize, rel_arg: usize, - set: LocalSet, sig: Sig, } @@ -71,14 +72,10 @@ impl<'a> Writer<'_, 'a> { else { return Err(format!("function {index} is not a definition")); }; - let Recursion::Structural { - argument, - mutual: _, - } = recursion - else { + let Recursion::Structural { argument } = recursion else { return Err(format!("`{module}.{name}` is not structurally recursive")); }; - let clocals = Gen::scope(&locals, ""); + let clocals = Gen::scope(&locals); let arg_position = clocals .iter() .position(|local| local.local.name == argument) @@ -88,7 +85,7 @@ impl<'a> Writer<'_, 'a> { let sig = self.signature(*index, &clocals, &set, &result, denotation)?; self.sigs.insert(*index, sig.clone()); let width = |local: &CLocal, rel: bool| -> usize { - if local.companion.is_some() { + if local.has_companion { if rel { 5 } else { @@ -107,8 +104,6 @@ impl<'a> Writer<'_, 'a> { index: *index, module, name, - type_arguments, - result, body, site, type_parameters, @@ -118,7 +113,6 @@ impl<'a> Writer<'_, 'a> { plan_position: 0, fits_arg, rel_arg, - set, sig, }); } @@ -414,18 +408,6 @@ impl<'a> Writer<'_, 'a> { } } - /// `T.casesOn` at the constant motive `result` whose minors are the - /// functions of the constructor's fields. - fn cases_on_fresh_motive( - &mut self, - ty: &SemanticType, - discriminant: Term, - result: Term, - minors: &[Term], - ) -> Result { - self.cases_on_const(ty, discriminant, result, minors) - } - /// The denotation of a definition instance applied to its locals. fn denotation_of( &mut self, @@ -484,7 +466,7 @@ impl<'a> Writer<'_, 'a> { at_position: &[Vec], position: usize, ctor: usize, - ) -> Result<(Term, Term, Vec<(usize, Term)>), String> { + ) -> Result { let shape = &plan.positions[position].constructors[ctor]; let slots = &at_position[position]; // Free variables: the fields, and a hypothesis for each recursive @@ -580,7 +562,9 @@ impl<'a> Writer<'_, 'a> { /// relation, each abstracted over the other arguments, and the direct /// form of the predicate over the fields; with the value of the /// constructor. - #[allow(clippy::too_many_lines)] + // The minor's inputs are the plan, the group, and one constructor's + // fields; bundling them would only rename the same nine values. + #[allow(clippy::too_many_lines, clippy::too_many_arguments)] fn structural_member_minor( &mut self, plan: &RecPlan, @@ -607,7 +591,48 @@ impl<'a> Writer<'_, 'a> { }; match scrutinee.as_ref() { SemanticTerm::Var { name } if *name == member.argument => {} - _ => { + SemanticTerm::Var { .. } + | SemanticTerm::Nat { .. } + | SemanticTerm::Integer { .. } + | SemanticTerm::String { .. } + | SemanticTerm::Bytes { .. } + | SemanticTerm::Primitive { .. } + | SemanticTerm::Bool { .. } + | SemanticTerm::Unit + | SemanticTerm::Nil { .. } + | SemanticTerm::Cons { .. } + | SemanticTerm::Record { .. } + | SemanticTerm::Constructor { .. } + | SemanticTerm::InstanceValue { .. } + | SemanticTerm::Project { .. } + | SemanticTerm::Call { .. } + | SemanticTerm::If { .. } + | SemanticTerm::Match { .. } + | SemanticTerm::Eq { .. } + | SemanticTerm::Le { .. } + | SemanticTerm::Lt { .. } + | SemanticTerm::Add { .. } + | SemanticTerm::Beq { .. } + | SemanticTerm::Ble { .. } + | SemanticTerm::Blt { .. } + | SemanticTerm::And { .. } + | SemanticTerm::PropAnd { .. } + | SemanticTerm::Or { .. } + | SemanticTerm::Not { .. } + | SemanticTerm::Implies { .. } + | SemanticTerm::Iff { .. } + | SemanticTerm::Forall { .. } + | SemanticTerm::Let { .. } + | SemanticTerm::Pair { .. } + | SemanticTerm::First { .. } + | SemanticTerm::Second { .. } + | SemanticTerm::Lambda { .. } + | SemanticTerm::Apply { .. } + | SemanticTerm::FunctionRef { .. } + | SemanticTerm::MapLiteral { .. } + | SemanticTerm::SetLiteral { .. } + | SemanticTerm::GraphLiteral { .. } + | SemanticTerm::CheckedApply { .. } => { return Err(format!( "structural `{}.{}` does not match on `{}`", member.module, member.name, member.argument @@ -669,7 +694,7 @@ impl<'a> Writer<'_, 'a> { ty, }, generic, - companion: None, + has_companion: false, }); terms.push(LocalTerm { value: *free, @@ -709,7 +734,20 @@ impl<'a> Writer<'_, 'a> { scrutinee, arms, } => (ty, scrutinee.as_ref(), arms.as_slice()), - _ => return Err("the lowering of a structural body is not a match".to_owned()), + Expr::Value { .. } + | Expr::Var { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => { + return Err("the lowering of a structural body is not a match".to_owned()) + } }; let arm = arms .get(branch_index) @@ -744,7 +782,20 @@ impl<'a> Writer<'_, 'a> { let p = self.prog; let number = match scrutinee_expr { Expr::Var { name } => *name, - _ => return Err("the lowering of a structural scrutinee is not a variable".to_owned()), + Expr::Value { .. } + | Expr::Let { .. } + | Expr::Cond { .. } + | Expr::Match { .. } + | Expr::Build { .. } + | Expr::Call { .. } + | Expr::Closure { .. } + | Expr::Apply { .. } + | Expr::Prim { .. } + | Expr::First { .. } + | Expr::Second { .. } + | Expr::Field { .. } => { + return Err("the lowering of a structural scrutinee is not a variable".to_owned()) + } }; let x = self.nat_u(number); let tested = self.apply( @@ -836,13 +887,13 @@ impl<'a> Writer<'_, 'a> { // The component types of each position, with the free variables that // stand for the argument of each function there. let mut tys_cache: HashMap<(usize, bool), (Vec, Vec)> = HashMap::new(); - for position in 0..plan.positions.len() { + for (position, slots) in at_position.iter().enumerate().take(plan.positions.len()) { for relation in [false, true] { - if at_position[position].is_empty() { + if slots.is_empty() { continue; } - let tys = self.component_types(members, &at_position[position], relation)?; - let frees: Vec = at_position[position] + let tys = self.component_types(members, slots, relation)?; + let frees: Vec = slots .iter() .map(|slot| { let other = &members[*slot]; @@ -856,7 +907,7 @@ impl<'a> Writer<'_, 'a> { tys_cache.insert((position, relation), (tys, frees)); } } - let mut replace = |writer: &mut Self, term: Term, relation: bool| -> Term { + let replace = |writer: &mut Self, term: Term, relation: bool| -> Term { let called = &called; let by_field = &by_field; let tys_cache = &tys_cache; diff --git a/crates/lexlean/src/production/certificate/data_a/wf.rs b/crates/lexlean/src/production/certificate/data_a/wf.rs index ec2165958..68a90bca4 100644 --- a/crates/lexlean/src/production/certificate/data_a/wf.rs +++ b/crates/lexlean/src/production/certificate/data_a/wf.rs @@ -234,7 +234,7 @@ impl<'a> Writer<'_, 'a> { "`{module}.{name}` names a termination measure and does not recurse" )); } - let clocals: Vec = Gen::scope(locals, ""); + let clocals: Vec = Gen::scope(locals); let declared: Vec = type_parameters.iter().map(|(n, _)| n.clone()).collect(); let parameters: Vec = locals .iter() @@ -431,7 +431,19 @@ impl<'a> Writer<'_, 'a> { let value_node = self.b.node(body_value).clone(); let denoted = match value_node { crate::ir::core::CoreNode::App { f, x: denoted } if f == encoder => denoted, - _ => return Err("the value of a well-founded body is not an encoding".to_owned()), + crate::ir::core::CoreNode::BVar { .. } + | crate::ir::core::CoreNode::Sort { .. } + | crate::ir::core::CoreNode::Const { .. } + | crate::ir::core::CoreNode::App { .. } + | crate::ir::core::CoreNode::Lambda { .. } + | crate::ir::core::CoreNode::Forall { .. } + | crate::ir::core::CoreNode::Let { .. } + | crate::ir::core::CoreNode::Nat { .. } + | crate::ir::core::CoreNode::String { .. } + | crate::ir::core::CoreNode::Mdata { .. } + | crate::ir::core::CoreNode::Projection { .. } => { + return Err("the value of a well-founded body is not an encoding".to_owned()) + } }; let lean_result = self.lean_ty(result)?; let value_var = self.b.free(); @@ -448,7 +460,18 @@ impl<'a> Writer<'_, 'a> { let (definition, definition_args) = self.b.spine(denotation); let definition_name = match self.b.node(definition).clone() { crate::ir::core::CoreNode::Const { n, .. } => n, - _ => return Err("the denotation of a definition is not a constant".to_owned()), + crate::ir::core::CoreNode::BVar { .. } + | crate::ir::core::CoreNode::Sort { .. } + | crate::ir::core::CoreNode::App { .. } + | crate::ir::core::CoreNode::Lambda { .. } + | crate::ir::core::CoreNode::Forall { .. } + | crate::ir::core::CoreNode::Let { .. } + | crate::ir::core::CoreNode::Nat { .. } + | crate::ir::core::CoreNode::String { .. } + | crate::ir::core::CoreNode::Mdata { .. } + | crate::ir::core::CoreNode::Projection { .. } => { + return Err("the denotation of a definition is not a constant".to_owned()) + } }; let eq_def_head = self.b.konst(&format!("{definition_name}.eq_def")); let eq_def = self.b.apps(eq_def_head, &definition_args); diff --git a/crates/lexlean/src/production/kernel/ext.rs b/crates/lexlean/src/production/kernel/ext.rs index e7d845541..fea322ba7 100644 --- a/crates/lexlean/src/production/kernel/ext.rs +++ b/crates/lexlean/src/production/kernel/ext.rs @@ -24,6 +24,10 @@ use std::collections::HashMap; use super::{Applied, Builder, Term}; use crate::ir::core::{CoreBinderInfo, CoreNode}; +/// The rewriter of `rewrite_apps`: given a constant's name and its rewritten +/// arguments, the replacement, or `None` to keep the application. +type Rewrite<'r> = dyn FnMut(&mut Builder, &str, &[Term]) -> Option + 'r; + const FREE: &str = "\u{1}free."; const META: &str = "\u{1}meta."; @@ -271,11 +275,7 @@ impl Builder { /// which is given the constant's name and its (already rewritten) /// arguments and returns the replacement, or `None` to keep the /// application. - pub fn rewrite_apps( - &mut self, - term: Term, - rewrite: &mut dyn FnMut(&mut Builder, &str, &[Term]) -> Option, - ) -> Term { + pub fn rewrite_apps(&mut self, term: Term, rewrite: &mut Rewrite<'_>) -> Term { let mut memo = HashMap::new(); self.rewrite_memo(term, rewrite, &mut memo) } @@ -283,7 +283,7 @@ impl Builder { fn rewrite_memo( &mut self, term: Term, - rewrite: &mut dyn FnMut(&mut Builder, &str, &[Term]) -> Option, + rewrite: &mut Rewrite<'_>, memo: &mut HashMap, ) -> Term { if let Some(&done) = memo.get(&term) { @@ -808,11 +808,8 @@ impl Builder { next: 0, }; let mut ty = ty; - loop { - let (binder, info, domain, body) = match &self.nodes[ty] { - CoreNode::Forall { n, b, t, v } => (n.clone(), *b, *t, *v), - _ => break, - }; + while let CoreNode::Forall { n, b, t, v } = &self.nodes[ty] { + let (binder, info, domain, body) = (n.clone(), *b, *t, *v); let (id, placeholder) = self.meta(); if let Some((_, hint)) = hints.iter().find(|(wanted, _)| *wanted == binder) { open.solved.insert(id, *hint); From deaa451f5d1de50d1162e7de39d4355bc6c1d0cd Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 19:34:00 +0000 Subject: [PATCH 21/41] #89 stage 3: the conformance cases read certificate A as data (SP-10 rows, SP-03 witnesses) Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- crates/conformance/src/cases/preservation.rs | 41 ++++++++++++++------ crates/conformance/src/preservation.rs | 27 +++++++------ 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/crates/conformance/src/cases/preservation.rs b/crates/conformance/src/cases/preservation.rs index a06e05199..aa8fd49f0 100644 --- a/crates/conformance/src/cases/preservation.rs +++ b/crates/conformance/src/cases/preservation.rs @@ -3024,21 +3024,38 @@ pub fn run(id: &str) { .iter() .find(|entry| entry.root.ends_with("groveRoot")) .expect("the recursive-tree root is certified"); + let data = grove + .certificate + .data + .as_ref() + .expect("certificate A is data"); + let module = &grove.certificate.module; + let rows: Vec<&lexlean::ir::core::CoreDeclaration> = data.declarations.iter().collect(); + let has = |short: &str| rows.iter().any(|row| row.name.starts_with(&format!("{module}.{short}"))); + // The recursive group's validators are one mutual group of rows. + assert!( + rows.iter().any(|row| { + row.name.starts_with(&format!("{module}.__valid_")) + && row.all.as_ref().is_some_and(|all| all.len() > 1) + }), + "the recursive tree's certificate states its validators as a mutual group" + ); for needed in [ - "mutual\ndef __valid_", - "theorem __viff_", - "theorem __vrel_", - "theorem __vinv_", - "LexLeanPreservation.invList_eqs", - "theorem entry_accepts", - "theorem entry_refuses", - "def accepts", + "__viff_", + "__vrel_", + "__vinv_", + "entry_accepts", + "entry_refuses", + "accepts", ] { - assert!( - grove.certificate.text.contains(needed), - "the recursive tree's certificate states `{needed}`" - ); + assert!(has(needed), "the recursive tree's certificate states `{needed}`"); } + assert!( + data.nodes.iter().any(|node| matches!(node, + lexlean::ir::core::CoreNode::Const { n, .. } + if n == "LexLeanPreservation.invList_eqs")), + "the recursive tree's certificate applies the library's invList_eqs" + ); // A validator called from inside the program is refused by the // lowering's audit, as is an entry the validators do not match. let checked = support::checked_project(&coverage); diff --git a/crates/conformance/src/preservation.rs b/crates/conformance/src/preservation.rs index 4726bed08..9709d821e 100644 --- a/crates/conformance/src/preservation.rs +++ b/crates/conformance/src/preservation.rs @@ -984,18 +984,21 @@ fn witnesses( // The names the encoders of the arguments unfold through: the ones // certificate A defines and the library's. let mut names: Vec = Vec::new(); - for line in certificate_a.text.lines() { - if let Some(rest) = line.strip_prefix("def ") { - let name: String = rest - .chars() - .take_while(|character| character.is_alphanumeric() || *character == '_') - .collect(); - if ["__enc_", "__aux_", "__items_", "__L_", "__O_"] - .iter() - .any(|prefix| name.starts_with(prefix)) - { - names.push(format!("{}.{name}", certificate_a.module)); - } + let data = certificate_a + .data + .as_ref() + .expect("certificate A is held as data"); + for row in &data.declarations { + let name = row + .name + .strip_prefix(&format!("{}.", certificate_a.module)) + .unwrap_or(&row.name); + if ["__enc_", "__aux_", "__items_", "__L_", "__O_"] + .iter() + .any(|prefix| name.starts_with(prefix)) + && !name.ends_with("._unsafe_rec") + { + names.push(row.name.clone()); } } for (at, found) in composed.text.match_indices("LexLeanPreservation.enc") { From 8a73c1cf0c251692142210dd5e116453909960bc Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 20:04:28 +0000 Subject: [PATCH 22/41] #89 stage 3: SP-02 reads the root theorem of certificate A as a row Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- crates/conformance/src/cases/preservation.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/conformance/src/cases/preservation.rs b/crates/conformance/src/cases/preservation.rs index aa8fd49f0..5133fb788 100644 --- a/crates/conformance/src/cases/preservation.rs +++ b/crates/conformance/src/cases/preservation.rs @@ -1608,7 +1608,12 @@ pub fn run(id: &str) { entry.root ); assert!( - entry.certificate.text.contains("theorem root"), + entry.certificate.data.as_ref().is_some_and(|data| { + data.declarations.iter().any(|row| { + row.name == format!("{}.root", entry.certificate.module) + && matches!(row.kind, lexlean::ir::core::CoreDeclKind::Theorem) + }) + }), "{}: the certificate states the root theorem", entry.root ); From d30c2467ceca006337c8af0b1fcb2cb2c729457e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 20:36:50 +0000 Subject: [PATCH 23/41] #89 stage 3: the certificate-rejected fixture plants its defect in certificate A's data Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- .../certificate-rejected/toolchain/bin/lake | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/negative/certificate-rejected/toolchain/bin/lake b/tests/negative/certificate-rejected/toolchain/bin/lake index 1004c28b7..bc2c6ba90 100755 --- a/tests/negative/certificate-rejected/toolchain/bin/lake +++ b/tests/negative/certificate-rejected/toolchain/bin/lake @@ -1,14 +1,17 @@ #!/bin/sh # Passes every invocation to the pinned lake except `lake env lean` on a -# preservation certificate, whose program literal first has every natural -# addition turned into a subtraction; the proof, derived from the source, -# no longer holds, and verification refuses the certificate (SPEC.md 17.17). +# certificate A, whose kernel data first has the natural-addition primitive of +# the target syntax turned into the subtraction, in the program literal and +# wherever the data names it; the lemma the proof applies still states the +# addition, the proof no longer holds, and verification refuses the +# certificate (SPEC.md 17.17). real="$(dirname "$(readlink -f "$(dirname "$0")/lean")")/lake" if [ "$1" = "env" ] && [ "$2" = "lean" ]; then for source; do :; done case "$source" in - */preserve-src/LexLeanPreserve/C*.lean) - sed 's/\.prim \.natAdd/.prim .natSub/g' "$source" > "$source.tmp" && mv "$source.tmp" "$source" + */preserve-src/LexLeanPreserve/C*/R*/*.lean) ;; + */preserve-src/LexLeanPreserve/C*/R[0-9]*.lean) + sed 's/TargetSyntax\.Prim\.natAdd/TargetSyntax.Prim.natSub/g' "$source" > "$source.tmp" && mv "$source.tmp" "$source" ;; esac fi From ea7569456e2530a4160b725365a9d4f924bb6452 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 21:52:17 +0000 Subject: [PATCH 24/41] #89 stage 3: SP-05 reads certificate A at its notation path; the resource-exhausted limit is raised while its size is measured Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- crates/conformance/src/cases/preservation.rs | 2 +- .../certificate-resource-exhausted/project/lexlean.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/conformance/src/cases/preservation.rs b/crates/conformance/src/cases/preservation.rs index bed45a63b..9e4de41c4 100644 --- a/crates/conformance/src/cases/preservation.rs +++ b/crates/conformance/src/cases/preservation.rs @@ -2328,7 +2328,7 @@ pub fn run(id: &str) { let module = row["module"].as_str().expect("a module"); let text = std::fs::read( root.join("preserve") - .join(lexlean::production::preserve::module_path(module)), + .join(lexlean::production::preserve::notation_path(module)), ) .expect("the published certificate"); assert_eq!( diff --git a/tests/negative/certificate-resource-exhausted/project/lexlean.toml b/tests/negative/certificate-resource-exhausted/project/lexlean.toml index 8bd4f6693..d4c3bbd75 100644 --- a/tests/negative/certificate-resource-exhausted/project/lexlean.toml +++ b/tests/negative/certificate-resource-exhausted/project/lexlean.toml @@ -14,7 +14,7 @@ package = "lexlean.std.nat" kind = "builtin" [limits] -max_file_bytes = 131072 +max_file_bytes = 4194304 max_total_source_bytes = 67108864 max_primitive_atoms = 2000000 max_token_lattice_edges = 4000000 From 5407b6b21113c5e73cbaec82a4d80bf8e4d26a33 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 21:56:46 +0000 Subject: [PATCH 25/41] #89 stage 3: the certificate-resource-exhausted limit is 1 MiB for the data certificate A Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- .../certificate-resource-exhausted/project/lexlean.lock | 4 ++-- .../certificate-resource-exhausted/project/lexlean.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/negative/certificate-resource-exhausted/project/lexlean.lock b/tests/negative/certificate-resource-exhausted/project/lexlean.lock index 42cdc8e6a..39892596b 100644 --- a/tests/negative/certificate-resource-exhausted/project/lexlean.lock +++ b/tests/negative/certificate-resource-exhausted/project/lexlean.lock @@ -1,7 +1,7 @@ spec = "lexlean/lock/2" language = "1.2" -compiler_semantics = "7c6ad91252b5e1a4d0db2bb0b8fd6bff660b68553b163bd3b5552b4694352d8e" -project_config_sha256 = "743b4fe5ccb7b0dcae4d1ddf7fed292c41f3bd01521f10a3295978ac215364cd" +compiler_semantics = "974c98b59fc09a23c87bba2bd495f516ffbb7ae78085ea62b7e2580f6d01a290" +project_config_sha256 = "4451f491e6cbbcac327c15fbcdead76619a961ba9d03b745d014a1acab199085" lean_toolchain = "leanprover/lean4:v4.32.1" [[workspace_file]] diff --git a/tests/negative/certificate-resource-exhausted/project/lexlean.toml b/tests/negative/certificate-resource-exhausted/project/lexlean.toml index d4c3bbd75..0fba2e93f 100644 --- a/tests/negative/certificate-resource-exhausted/project/lexlean.toml +++ b/tests/negative/certificate-resource-exhausted/project/lexlean.toml @@ -14,7 +14,7 @@ package = "lexlean.std.nat" kind = "builtin" [limits] -max_file_bytes = 4194304 +max_file_bytes = 1048576 max_total_source_bytes = 67108864 max_primitive_atoms = 2000000 max_token_lattice_edges = 4000000 From 703e71a11915278b85e0d282daf2c9429a5b133e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 21:59:18 +0000 Subject: [PATCH 26/41] #89 stage 3: the certificate-resource-exhausted lock is current Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- .../certificate-resource-exhausted/project/lexlean.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/negative/certificate-resource-exhausted/project/lexlean.lock b/tests/negative/certificate-resource-exhausted/project/lexlean.lock index 39892596b..213eec3eb 100644 --- a/tests/negative/certificate-resource-exhausted/project/lexlean.lock +++ b/tests/negative/certificate-resource-exhausted/project/lexlean.lock @@ -1,6 +1,6 @@ spec = "lexlean/lock/2" language = "1.2" -compiler_semantics = "974c98b59fc09a23c87bba2bd495f516ffbb7ae78085ea62b7e2580f6d01a290" +compiler_semantics = "7c6ad91252b5e1a4d0db2bb0b8fd6bff660b68553b163bd3b5552b4694352d8e" project_config_sha256 = "4451f491e6cbbcac327c15fbcdead76619a961ba9d03b745d014a1acab199085" lean_toolchain = "leanprover/lean4:v4.32.1" From dc15c0baad988af975aca0feecd270006137fad9 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 22:26:39 +0000 Subject: [PATCH 27/41] #89 stage 3: certificate A weighs its recorded notation against max_file_bytes Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- crates/conformance/src/cases/preservation.rs | 41 +++++++++++-------- crates/conformance/src/preservation.rs | 7 ++-- crates/lexlean/src/production/certificate.rs | 5 ++- crates/lexlean/src/verify/mod.rs | 17 +++++--- .../project/lexlean.lock | 2 +- .../project/lexlean.toml | 2 +- 6 files changed, 46 insertions(+), 28 deletions(-) diff --git a/crates/conformance/src/cases/preservation.rs b/crates/conformance/src/cases/preservation.rs index 9e4de41c4..45d9172c9 100644 --- a/crates/conformance/src/cases/preservation.rs +++ b/crates/conformance/src/cases/preservation.rs @@ -385,14 +385,7 @@ fn stress_estimates() -> u64 { let largest = |numerator: usize| -> u64 { preservation::certificates(&crate::stress::mixed_lifted(shape.scaled(numerator))) .iter() - .map(|entry| { - std::iter::once(&entry.certificate) - .chain(entry.renderings.iter().map(|(_, b)| b)) - .chain(entry.composed.iter().map(|(_, e)| e)) - .map(|certificate| certificate.text.len() as u64) - .max() - .unwrap_or(0) - }) + .map(largest_certificate) .max() .unwrap_or(0) }; @@ -455,7 +448,7 @@ fn stress_estimates() -> u64 { for entry in certified { assert_floor(&family, &entry); assert_bounds(&family, &entry); - let a = entry.certificate.text.len() as u64; + let a = weight_a(&entry.certificate); let e = entry .composed .iter() @@ -528,6 +521,25 @@ fn stress_estimates() -> u64 { }) } +/// The weight of certificate A against `max_file_bytes`: the module as +/// recorded, in the authoring notation, which is what the generator bounds. +fn weight_a(certificate: &lexlean::production::certificate::Certificate) -> u64 { + certificate + .source + .as_ref() + .map_or(certificate.text.len(), String::len) as u64 +} + +/// The size of the largest certificate of a root: A by its recorded weight, +/// B and E by their text. +fn largest_certificate(entry: &preservation::Certified) -> u64 { + std::iter::once(weight_a(&entry.certificate)) + .chain(entry.renderings.iter().map(|(_, b)| b.text.len() as u64)) + .chain(entry.composed.iter().map(|(_, e)| e.text.len() as u64)) + .max() + .unwrap_or(0) +} + /// The floor the lowering puts under certificate B from the entries of its /// field reads is no more than any certificate B generated. fn assert_floor(label: &str, entry: &preservation::Certified) { @@ -551,7 +563,7 @@ fn assert_bounds(label: &str, entry: &preservation::Certified) { let bounds = lexlean::production::lower::certificate_lower_bounds( &lexlean::production::lower::measure_program(&entry.program), ); - let a = entry.certificate.text.len() as u64; + let a = weight_a(&entry.certificate); let smallest = |texts: Vec| texts.into_iter().min().unwrap_or(u64::MAX); let b = smallest( entry @@ -1648,13 +1660,8 @@ pub fn run(id: &str) { let between = P::negative("certificate-resource-exhausted"); let limit = support::limits(&between).max_file_bytes; for entry in preservation::certificates(&between) { - let module = entry.certificate.text.len() as u64; - let largest = std::iter::once(&entry.certificate) - .chain(entry.renderings.iter().map(|(_, b)| b)) - .chain(entry.composed.iter().map(|(_, e)| e)) - .map(|certificate| certificate.text.len() as u64) - .max() - .unwrap_or(0); + let module = weight_a(&entry.certificate); + let largest = largest_certificate(&entry); assert!( largest <= limit && limit <= module.saturating_mul(4), "certificate-resource-exhausted: {}: its limit {limit} must lie between its largest certificate, {largest}, and four times its module, {}", diff --git a/crates/conformance/src/preservation.rs b/crates/conformance/src/preservation.rs index 9709d821e..a27c9539c 100644 --- a/crates/conformance/src/preservation.rs +++ b/crates/conformance/src/preservation.rs @@ -197,7 +197,7 @@ pub fn halved(project: &P, certified: &[Certified]) -> Vec<(String, String, Vec< root.report, &lowered, &module, - held.certificate.text.len() as u64 / 2, + held.certificate.source.as_ref().map_or(held.certificate.text.len(), String::len) as u64 / 2, )); let fallible = lexlean::calculus::rust::fallible_functions(&lowered.program) .expect("a valid program")[lowered.entry() as usize]; @@ -2890,9 +2890,10 @@ mod dev { ) { Ok(certificate) => { eprintln!( - "R{index} {}: built, {} bytes of Lean", + "R{index} {}: built, {} bytes of Lean, {} of notation", root.report.root, - certificate.text.len() + certificate.text.len(), + certificate.source.as_ref().map_or(0, String::len) ); built.push(certificate); } diff --git a/crates/lexlean/src/production/certificate.rs b/crates/lexlean/src/production/certificate.rs index 30a2bdb80..85f000f57 100644 --- a/crates/lexlean/src/production/certificate.rs +++ b/crates/lexlean/src/production/certificate.rs @@ -543,8 +543,11 @@ pub fn certificate( .map_err(failure)?; let text = crate::backend::core::lean_text_legacy(&data, module) .map_err(|diagnostic| internal(diagnostic.message))?; - generator.within(text.len()).map_err(failure)?; + // The certificate's weight is the module as recorded, in the authoring + // notation: the rendering to Lean text is the core backend's, and its size + // is the backend's, not a property of the certificate. let source = record_source(module, &data, "A").map_err(internal)?; + generator.within(source.len()).map_err(failure)?; let mut imports: Vec = modules .values() .map(|linked| linked.lean_module.to_owned()) diff --git a/crates/lexlean/src/verify/mod.rs b/crates/lexlean/src/verify/mod.rs index 984a0f8bd..06ec039e0 100644 --- a/crates/lexlean/src/verify/mod.rs +++ b/crates/lexlean/src/verify/mod.rs @@ -964,19 +964,26 @@ pub fn run_part( entry: lowered.entry(), }); } - for generated in std::iter::once(&generated).chain( + // Certificate A weighs what it is recorded as, the notation; B + // and E weigh their text. + let weight_a = generated + .source + .as_ref() + .map_or(generated.text.len(), String::len); + for (generated, weight) in std::iter::once((&generated, weight_a)).chain( renderings .iter() - .flat_map(|rendering| [&rendering.certificate, &rendering.composed]), + .flat_map(|rendering| [&rendering.certificate, &rendering.composed]) + .map(|certificate| (certificate, certificate.text.len())), ) { - if generated.text.len() as u64 > limits.max_file_bytes { + if weight as u64 > limits.max_file_bytes { return Err(fail(Diagnostic::new( code!("LLS8002"), format!( "max_file_bytes exceeded in phase certificates: configured {}, `{}` is {} bytes", limits.max_file_bytes, generated.module, - generated.text.len() + weight ), ))); } @@ -993,7 +1000,7 @@ pub fn run_part( .map_err(fail)?; } budget - .charge(generated.text.len(), &root.report.root, "its certificate A") + .charge(weight_a, &root.report.root, "its certificate A") .map_err(fail)?; certified.push(CertifiedRoot { root: root.report.root.clone(), diff --git a/tests/negative/certificate-resource-exhausted/project/lexlean.lock b/tests/negative/certificate-resource-exhausted/project/lexlean.lock index 213eec3eb..42cdc8e6a 100644 --- a/tests/negative/certificate-resource-exhausted/project/lexlean.lock +++ b/tests/negative/certificate-resource-exhausted/project/lexlean.lock @@ -1,7 +1,7 @@ spec = "lexlean/lock/2" language = "1.2" compiler_semantics = "7c6ad91252b5e1a4d0db2bb0b8fd6bff660b68553b163bd3b5552b4694352d8e" -project_config_sha256 = "4451f491e6cbbcac327c15fbcdead76619a961ba9d03b745d014a1acab199085" +project_config_sha256 = "743b4fe5ccb7b0dcae4d1ddf7fed292c41f3bd01521f10a3295978ac215364cd" lean_toolchain = "leanprover/lean4:v4.32.1" [[workspace_file]] diff --git a/tests/negative/certificate-resource-exhausted/project/lexlean.toml b/tests/negative/certificate-resource-exhausted/project/lexlean.toml index 0fba2e93f..8bd4f6693 100644 --- a/tests/negative/certificate-resource-exhausted/project/lexlean.toml +++ b/tests/negative/certificate-resource-exhausted/project/lexlean.toml @@ -14,7 +14,7 @@ package = "lexlean.std.nat" kind = "builtin" [limits] -max_file_bytes = 1048576 +max_file_bytes = 131072 max_total_source_bytes = 67108864 max_primitive_atoms = 2000000 max_token_lattice_edges = 4000000 From 99608744364a7693e0f3a89f77078c98ef26e0f5 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 22:43:10 +0000 Subject: [PATCH 28/41] #89 stage 3: certificate A data: the boundary derivation follows the source's validators and entry, so a planted defect is refused by the kernel --- crates/conformance/src/preservation.rs | 55 +++++- .../production/certificate/data_a/boundary.rs | 170 +++++++++++++++++- .../production/certificate/data_a/matching.rs | 4 +- 3 files changed, 220 insertions(+), 9 deletions(-) diff --git a/crates/conformance/src/preservation.rs b/crates/conformance/src/preservation.rs index 9709d821e..c56aa0380 100644 --- a/crates/conformance/src/preservation.rs +++ b/crates/conformance/src/preservation.rs @@ -1776,7 +1776,60 @@ fn expected_declarations( Origin::Definition { .. } | Origin::Instance { .. } | Origin::Lambda { .. } => { vec![format!("__rel_{function}")] } - Origin::Validator { .. } => vec![format!("__vrel_{function}")], + // The relations of validators that call one another are one + // recursion of the group's recursors, whose every row contains every + // minor premise: the kernel refuses the first of them it adds. + Origin::Validator { .. } => { + let calls = |from: usize| -> Vec { + // From the layout's kinds, which the source decides, not + // from the bodies, which a planted defect changes. + use lexlean::production::lower::Validation; + match &lowered.layout.functions[from] { + Origin::Validator { kind, .. } => match kind { + Validation::Trivial | Validation::Set => Vec::new(), + Validation::Map { value: callee } + | Validation::List { element: callee } + | Validation::Option { value: callee } => vec![*callee as usize], + Validation::Pair { left, right } => { + vec![*left as usize, *right as usize] + } + Validation::Result { ok, error } => vec![*ok as usize, *error as usize], + Validation::Document { fields } => fields + .iter() + .flatten() + .flatten() + .map(|callee| *callee as usize) + .collect(), + }, + _ => Vec::new(), + } + }; + let reach = |from: usize| -> Vec { + let mut seen = vec![from]; + let mut next = 0; + while next < seen.len() { + for callee in calls(seen[next]) { + if !seen.contains(&callee) + && matches!(lowered.layout.functions[callee], Origin::Validator { .. }) + { + seen.push(callee); + } + } + next += 1; + } + seen + }; + let forward = reach(function); + let mut group: Vec = forward + .into_iter() + .filter(|other| reach(*other).contains(&function)) + .collect(); + group.sort_unstable(); + group + .into_iter() + .map(|member| format!("__vrel_{member}")) + .collect() + } Origin::Entry { .. } => vec!["entry".to_owned()], Origin::Template { template, diff --git a/crates/lexlean/src/production/certificate/data_a/boundary.rs b/crates/lexlean/src/production/certificate/data_a/boundary.rs index eb0054b1a..0f6769b29 100644 --- a/crates/lexlean/src/production/certificate/data_a/boundary.rs +++ b/crates/lexlean/src/production/certificate/data_a/boundary.rs @@ -23,7 +23,7 @@ use super::super::{checked, components, element_of, option_value, parts, Validat use super::recplan::{PositionKind, RecCtor, RecPlan}; use super::rows::Sig; use super::{Pf, RowKind, Writer, EXPLICIT}; -use crate::calculus::{Expr, Ty}; +use crate::calculus::{Expr, Function, Shape, Ty}; use crate::ir::core::CoreLevel; use crate::ir::semantic::SemanticType; use crate::production::kernel::{Given, Term}; @@ -1547,13 +1547,14 @@ impl<'a> Writer<'_, 'a> { .parameters .first() .ok_or("a validator without a parameter")?; + let expected = self.expected_validator(validator, function)?; let body = match spec.arm { Some(arm_index) => { let Expr::Match { ty: match_ty, scrutinee, arms, - } = &function.body + } = &expected.body else { return Err(format!( "the validator {} was lowered to something other than a match", @@ -1605,7 +1606,7 @@ impl<'a> Writer<'_, 'a> { vars: vec![(parameter, encoded)], pair: Some((fields[0], fields[1])), }; - self.chain_pf(&binding, base, &function.body, &checks, spec.direct)? + self.chain_pf(&binding, base, &expected.body, &checks, spec.direct)? } }; let function_term = self.function_term(function)?; @@ -1630,9 +1631,164 @@ impl<'a> Writer<'_, 'a> { ], &[Given::Refl, Given::Refl, Given::Proof(body.proof, body.ty)], )?; + self.forget_expression(&expected.body); Ok(introduced.proof) } + /// The function the lowering builds for a container or document + /// validator, from the layout's kind and the same builders the lowering + /// uses. The program's own function is only read for the types of its + /// parameter: the derivation follows what the source calls for, so a + /// program that differs is refused by the kernel where the derived body + /// meets the program's. + fn expected_validator( + &self, + validator: &Validator, + program_function: &Function, + ) -> Result { + use crate::calculus::library::validators; + let index = validator.index; + let parameter = program_function + .types + .first() + .ok_or("a validator without a parameter type")?; + let mismatch = + || format!("the validator {index} has a parameter type its kind cannot have"); + Ok(match &validator.kind { + Validation::List { element } => { + let Ty::List { element: ty } = parameter else { + return Err(mismatch()); + }; + validators::list_of(index, *element, ty) + } + Validation::Option { value } => { + let Ty::Option { value: ty } = parameter else { + return Err(mismatch()); + }; + validators::option_of(*value, ty) + } + Validation::Pair { left, right } => { + let Ty::Pair { left: a, right: b } = parameter else { + return Err(mismatch()); + }; + validators::pair_of(*left, *right, a, b) + } + Validation::Result { ok, error } => { + let Ty::Result { ok: a, error: b } = parameter else { + return Err(mismatch()); + }; + validators::result_of(*ok, *error, a, b) + } + Validation::Document { fields } => { + let Ty::Adt { index: adt } = parameter else { + return Err(mismatch()); + }; + validators::document(*adt, fields) + } + Validation::Trivial | Validation::Set | Validation::Map { value: _ } => { + return Err(format!( + "the validator {index} is not written by a derivation" + )); + } + }) + } + + /// The body the lowering gives an entry: each checked parameter's + /// validator called on it with the refusal otherwise, around the root's + /// call, as `some`. + fn expected_entry( + &self, + program_function: &Function, + validators: &[Option], + ) -> Result { + let Ty::Option { value: _ } = &program_function.result else { + return Err("the entry does not return an option".to_owned()); + }; + let outcome = program_function.result.clone(); + let arguments = (0..validators.len() as u64) + .map(|name| Expr::Var { name }) + .collect(); + let mut body = Expr::Build { + shape: Shape::Some, + ty: outcome.clone(), + operands: vec![Expr::Call { + function: 0, + operands: arguments, + }], + }; + for (position, validator) in validators.iter().enumerate().rev() { + if let Some(validator) = validator { + body = Expr::Cond { + condition: Box::new(Expr::Call { + function: *validator, + operands: vec![Expr::Var { + name: position as u64, + }], + }), + then_branch: Box::new(body), + else_branch: Box::new(Expr::Build { + shape: Shape::None, + ty: outcome.clone(), + operands: Vec::new(), + }), + }; + } + } + Ok(body) + } + + /// Drop the renderings cached for the parts of an expression that is + /// about to be freed: the cache is keyed by address, and a later + /// expression built at a freed address must not meet them. + fn forget_expression(&mut self, expr: &Expr) { + self.exprs.remove(&std::ptr::from_ref(expr)); + match expr { + Expr::Let { bound, body, .. } => { + self.forget_expression(bound); + self.forget_expression(body); + } + Expr::Cond { + condition, + then_branch, + else_branch, + } => { + self.forget_expression(condition); + self.forget_expression(then_branch); + self.forget_expression(else_branch); + } + Expr::Match { + scrutinee, arms, .. + } => { + self.forget_expression(scrutinee); + for arm in arms { + self.forget_expression(&arm.body); + } + } + Expr::Build { operands, .. } + | Expr::Call { operands, .. } + | Expr::Prim { operands, .. } => { + for operand in operands { + self.forget_expression(operand); + } + } + Expr::Closure { captures, .. } => { + for capture in captures { + self.forget_expression(capture); + } + } + Expr::Apply { target, operands } => { + self.forget_expression(target); + for operand in operands { + self.forget_expression(operand); + } + } + Expr::First { value } | Expr::Second { value } | Expr::Field { value, .. } => { + self.forget_expression(value); + } + Expr::Value { .. } | Expr::Var { .. } => {} + } + } + /// `conv_var` for the variable `name`, bound to `value` in `env`. fn variable_pf(&mut self, env: Term, name: u64, value: Term) -> Result { let x = self.nat_u(name); @@ -1767,7 +1923,7 @@ impl<'a> Writer<'_, 'a> { &mut self, binding: &Binding, env: Term, - expr: &'a Expr, + expr: &Expr, checks: &[Checked], direct: bool, ) -> Result { @@ -1978,8 +2134,9 @@ impl<'a> Writer<'_, 'a> { } let empty = self.empty_env(); let env = self.extend_env(empty, &function.parameters, &encoded)?; - let mut layers: Vec<(usize, u64, &'a Expr, &'a Expr)> = Vec::new(); - let mut cursor = &function.body; + let expected_body = self.expected_entry(function, validators)?; + let mut layers: Vec<(usize, u64, &Expr, &Expr)> = Vec::new(); + let mut cursor = &expected_body; for (position, validator) in &checked { let Expr::Cond { condition, @@ -2143,6 +2300,7 @@ impl<'a> Writer<'_, 'a> { Given::Proof(current.proof, current.ty), ], )?; + self.forget_expression(&expected_body); let run = self.apply( "run_of_funRel", &[], diff --git a/crates/lexlean/src/production/certificate/data_a/matching.rs b/crates/lexlean/src/production/certificate/data_a/matching.rs index 27cc87c24..aabd2594e 100644 --- a/crates/lexlean/src/production/certificate/data_a/matching.rs +++ b/crates/lexlean/src/production/certificate/data_a/matching.rs @@ -18,7 +18,7 @@ impl<'a> Writer<'_, 'a> { &mut self, env: Term, encoded: Term, - arms: &'a [Arm], + arms: &[Arm], position: usize, fields: &[Term], extended: Term, @@ -55,7 +55,7 @@ impl<'a> Writer<'_, 'a> { &mut self, env: Term, encoded: Term, - arms: &'a [Arm], + arms: &[Arm], position: usize, later: Pf, ) -> Result { From 3e70d97295142928e689e126db49d44fb0d6c799 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 00:46:46 +0000 Subject: [PATCH 29/41] wip: the sizes of certificate A by stress family (local, not pushed) Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- crates/conformance/src/preservation.rs | 52 ++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/crates/conformance/src/preservation.rs b/crates/conformance/src/preservation.rs index 090b0666f..b0fd32909 100644 --- a/crates/conformance/src/preservation.rs +++ b/crates/conformance/src/preservation.rs @@ -3151,4 +3151,56 @@ mod dev { bodies.1 ); } + + /// The sizes of certificate A, as Lean text and as recorded, for each + /// stress family, with the limit lifted: the figures the size gates are + /// calibrated against. + #[test] + #[ignore = "a developer's lens on the sizes of certificate A"] + fn dev_family_sizes() { + std::thread::Builder::new() + .stack_size(1 << 30) + .spawn(family_sizes) + .expect("a thread") + .join() + .expect("the sizes"); + } + + fn family_sizes() { + for (family, project) in crate::stress::families() { + let checked = support::checked_project(&project); + let modules = linked_modules(&checked); + let hex32: String = checked.semantic_id.to_hex()[..32].to_owned(); + let limits = support::limits(&project); + for (index, root) in roots(&checked) + .expect("the eligibility reports") + .iter() + .enumerate() + { + let lowered = lower_root(&modules, &root.module, &root.name, root.report, &limits) + .expect("lowering"); + let module = format!("LexLeanPreserve.C{hex32}.R{index}"); + match certificate( + &modules, + &root.module, + &root.name, + root.report, + &lowered, + &module, + UNBOUNDED, + ) { + Ok(certificate) => eprintln!( + "FAMILY {family}: {}: {} text, {} notation (limit {})", + root.report.root, + certificate.text.len(), + certificate.source.as_ref().map_or(0, String::len), + limits.max_file_bytes + ), + Err(diagnostic) => { + eprintln!("FAMILY {family}: {}: {}", root.report.root, diagnostic.message); + } + } + } + } + } } From 1ecbff0096b7ab87321d8dc9a8356f72e78de12e Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 00:59:45 +0000 Subject: [PATCH 30/41] wip: the sizes of the mixed stress shapes (local, not pushed) Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- crates/conformance/src/preservation.rs | 46 ++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/crates/conformance/src/preservation.rs b/crates/conformance/src/preservation.rs index b0fd32909..0460798a8 100644 --- a/crates/conformance/src/preservation.rs +++ b/crates/conformance/src/preservation.rs @@ -3203,4 +3203,50 @@ mod dev { } } } + + /// The weights of the parts of the certificates of each mixed shape at a + /// scaling (`LEXLEAN_SCALE`, default 64/256), with the limits lifted. + #[test] + #[ignore = "a developer's lens on the sizes of certificate A"] + fn dev_mixed_sizes() { + std::thread::Builder::new() + .stack_size(1 << 30) + .spawn(|| { + let scale: usize = std::env::var("LEXLEAN_SCALE") + .ok() + .and_then(|value| value.parse().ok()) + .unwrap_or(64); + for (index, shape) in crate::stress::mixed_shapes().into_iter().enumerate() { + let project = crate::stress::mixed_lifted(shape.scaled(scale)); + let checked = support::checked_project(&project); + let modules = linked_modules(&checked); + let limits = support::limits(&project); + for (root_index, root) in roots(&checked).expect("reports").iter().enumerate() { + let lowered = + lower_root(&modules, &root.module, &root.name, root.report, &limits) + .expect("lowering"); + let module = format!("LexLeanPreserve.C{}.R{root_index}", "x".repeat(32)); + match certificate( + &modules, + &root.module, + &root.name, + root.report, + &lowered, + &module, + UNBOUNDED, + ) { + Ok(a) => eprintln!( + "MIXED {index} at {scale}: A {} text, {} notation", + a.text.len(), + a.source.as_ref().map_or(0, String::len) + ), + Err(diagnostic) => eprintln!("MIXED {index}: {}", diagnostic.message), + } + } + } + }) + .expect("a thread") + .join() + .expect("the sizes"); + } } From 3a5a3d6ab765b16ac92b872ced8540125bf56b46 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 01:03:20 +0000 Subject: [PATCH 31/41] wip: the size-limit message names the certificate (local, not pushed) Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- crates/lexlean/src/production/certificate.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/lexlean/src/production/certificate.rs b/crates/lexlean/src/production/certificate.rs index 85f000f57..13abfca0b 100644 --- a/crates/lexlean/src/production/certificate.rs +++ b/crates/lexlean/src/production/certificate.rs @@ -407,7 +407,7 @@ pub fn certificate_e( .map_err(failure)?; let text = crate::backend::core::lean_text_legacy(&data, module) .map_err(|diagnostic| internal(diagnostic.message))?; - generator.within(text.len()).map_err(failure)?; + generator.within(text.len(), "certificate E").map_err(failure)?; let source = record_source(module, &data, "E").map_err(internal)?; Ok(Certificate { module: module.to_owned(), @@ -547,7 +547,7 @@ pub fn certificate( // notation: the rendering to Lean text is the core backend's, and its size // is the backend's, not a property of the certificate. let source = record_source(module, &data, "A").map_err(internal)?; - generator.within(source.len()).map_err(failure)?; + generator.within(source.len(), "certificate A").map_err(failure)?; let mut imports: Vec = modules .values() .map(|linked| linked.lean_module.to_owned()) @@ -1462,10 +1462,10 @@ impl Gen<'_> { impl Gen<'_> { /// Refuse a part of a certificate of `len` bytes when it is larger than /// the certificate may be. - fn within(&self, len: usize) -> Result<(), String> { + fn within(&self, len: usize, what: &str) -> Result<(), String> { if len as u64 > self.limit { return Err(format!( - "{}a part of the certificate is {len} bytes, beyond the {} bytes of max_file_bytes", + "{}a part of {what} is {len} bytes, beyond the {} bytes of max_file_bytes", super::lower::LIMIT, self.limit )); From c07981f1cc7b4ea1541ec15973b9a1516013d17a Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 02:33:59 +0000 Subject: [PATCH 32/41] wip: the edge of the mixed shapes by certificate A alone (local, not pushed) Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- crates/conformance/src/preservation.rs | 63 ++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/crates/conformance/src/preservation.rs b/crates/conformance/src/preservation.rs index 0460798a8..44205e9bf 100644 --- a/crates/conformance/src/preservation.rs +++ b/crates/conformance/src/preservation.rs @@ -3249,4 +3249,67 @@ mod dev { .join() .expect("the sizes"); } + + /// Per mixed shape, the largest scaling whose certificate A fits the + /// default limit, searched on certificate A alone, and the weight of + /// certificate A there under the lifted and the default project. + #[test] + #[ignore = "a developer's lens on the sizes of certificate A"] + fn dev_mixed_edge() { + std::thread::Builder::new() + .stack_size(1 << 30) + .spawn(|| { + let weight = |project: &P| -> Result { + let checked = support::checked_project(project); + let modules = linked_modules(&checked); + let limits = support::limits(project); + let root = roots(&checked).expect("reports").remove(0); + let lowered = lower_root(&modules, &root.module, &root.name, root.report, &limits) + .expect("lowering"); + let module = format!("LexLeanPreserve.C{}.R0", "x".repeat(32)); + certificate( + &modules, + &root.module, + &root.name, + root.report, + &lowered, + &module, + limits.max_file_bytes, + ) + .map(|a| a.source.as_ref().map_or(0, String::len) as u64) + .map_err(|diagnostic| diagnostic.message) + }; + let limit = support::limits(&crate::stress::wide_match_default(2)).max_file_bytes; + let only: Option = std::env::var("LEXLEAN_SHAPE").ok().and_then(|v| v.parse().ok()); + for (index, shape) in crate::stress::mixed_shapes().into_iter().enumerate() { + if only.is_some_and(|only| only != index) { + continue; + } + let (mut low, mut high) = (1_usize, 256_usize); + let lifted = |n: usize| { + weight(&crate::stress::mixed_lifted(shape.scaled(n))).unwrap_or(u64::MAX) + }; + if lifted(high) <= limit { + low = high; + } else { + while high - low > 1 { + let middle = (low + high) / 2; + if lifted(middle) <= limit { + low = middle; + } else { + high = middle; + } + } + } + eprintln!( + "EDGE {index}: low {low}, lifted {}, default {:?}", + lifted(low), + weight(&crate::stress::mixed_default(shape.scaled(low))) + ); + } + }) + .expect("a thread") + .join() + .expect("the sizes"); + } } From 113129b18432d04a98969ad1a78cf61bc0ff548d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 04:37:54 +0000 Subject: [PATCH 33/41] wip: the wide-match sizes of certificate A (local, not pushed) Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- crates/conformance/src/preservation.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/conformance/src/preservation.rs b/crates/conformance/src/preservation.rs index 44205e9bf..687f50eab 100644 --- a/crates/conformance/src/preservation.rs +++ b/crates/conformance/src/preservation.rs @@ -3167,7 +3167,14 @@ mod dev { } fn family_sizes() { - for (family, project) in crate::stress::families() { + let mut projects = crate::stress::families(); + if std::env::var("LEXLEAN_WIDE").is_ok() { + projects = [2, 100, 101, 180] + .iter() + .map(|arms| (format!("wide match {arms}"), crate::stress::wide_match_default(*arms))) + .collect(); + } + for (family, project) in projects { let checked = support::checked_project(&project); let modules = linked_modules(&checked); let hex32: String = checked.semantic_id.to_hex()[..32].to_owned(); From c4c579fbfa4d866a4c94cc5b9d4ea4a8771e9a1c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 04:43:39 +0000 Subject: [PATCH 34/41] wip: the fitting enumeration is measured by count (local, not pushed) Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- crates/conformance/src/preservation.rs | 12 ++++++++++++ crates/conformance/src/stress.rs | 15 +++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/crates/conformance/src/preservation.rs b/crates/conformance/src/preservation.rs index 687f50eab..092bbaee9 100644 --- a/crates/conformance/src/preservation.rs +++ b/crates/conformance/src/preservation.rs @@ -3168,6 +3168,18 @@ mod dev { fn family_sizes() { let mut projects = crate::stress::families(); + if let Ok(list) = std::env::var("LEXLEAN_ENUM") { + projects = list + .split(',') + .filter_map(|item| item.trim().parse::().ok()) + .map(|count| { + ( + format!("enumeration of {count} constructors"), + crate::stress::fitting_enumeration(count), + ) + }) + .collect(); + } if std::env::var("LEXLEAN_WIDE").is_ok() { projects = [2, 100, 101, 180] .iter() diff --git a/crates/conformance/src/stress.rs b/crates/conformance/src/stress.rs index 8517432dc..845fcf2b8 100644 --- a/crates/conformance/src/stress.rs +++ b/crates/conformance/src/stress.rs @@ -1254,6 +1254,17 @@ pub fn random_shapes(count: usize, seed: u64) -> Vec { .collect() } +/// The constructors of the widest enumeration whose certificate A, recorded in +/// the authoring notation, fits under the default limit. +pub const FITTING_CONSTRUCTORS: usize = 420; + +/// A program that matches on an enumeration of `constructors` constructors, +/// under the default limits. +#[must_use] +pub fn fitting_enumeration(constructors: usize) -> P { + project_limited(&enumeration(constructors, 0), false) +} + /// Programs that are valid and whose certificates fit under the default /// limits, with the default limits: none may be refused. A match on 420 /// constructors (certificate A of 4.1 MB of the 4 MiB), a string literal of @@ -1265,8 +1276,8 @@ pub fn fitting() -> Vec<(String, P)> { let ascii = json!({"kind": "string", "value": "a".repeat(450_000)}); vec![ ( - "enumeration of 420 constructors".to_owned(), - project_limited(&enumeration(420, 0), false), + format!("enumeration of {FITTING_CONSTRUCTORS} constructors"), + fitting_enumeration(FITTING_CONSTRUCTORS), ), ( "string literal of 450000 characters".to_owned(), From 8fe88004d39fc9dd98c810311e3d646844576c87 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 05:03:50 +0000 Subject: [PATCH 35/41] wip: the fitting enumeration is 240 constructors, recorded in the notation (local, not pushed) Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- SPEC.md | 9 ++++----- VERIFICATION.md | 4 ++-- crates/conformance/src/stress.rs | 6 +++--- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/SPEC.md b/SPEC.md index aa323c968..c7762afcd 100644 --- a/SPEC.md +++ b/SPEC.md @@ -4744,7 +4744,7 @@ were fitted to, and requires that the largest scaling of each of nine mixed shapes whose certificates are all under the default limit, a program at the edge of what fits, is not refused. The early refusal refuses, and only refuses early: a root whose certificates fit under `max_file_bytes` is not -refused by it (a match on 420 constructors, whose certificate A is 4.0 MB; a +refused by it (a match on 240 constructors, whose certificate A is 3.9 MB in the notation it is recorded in; a string literal of 450 000 characters, whose certificate B is 1.4 MB; a match on 350 constructors returned with a string of 940 000 characters), and what the bound does not see, the guarantee below stops. Certificate B is not bounded @@ -4871,11 +4871,10 @@ listed by `tasklist` (then it had already exited), and the console delivers Ctrl the children. A match on 180 constructors verifies (`conformance_sp_02`), and so does one on 300, whose certificate B alone takes 3 minutes and a half; the reference machine reaches the 300 seconds between 300 -and 400 constructors, where `lexlean verify` of a match on 400 ends with +and 400 constructors, where, with `max_file_bytes` raised, `lexlean verify` of a match on 400 ends with `error[LLS8002]: child_timeout_ms exceeded by lean in phase -LexLeanPreserve.C….R0` (certificate A, of 3.7 MB). The limit `max_file_bytes` -itself allows about 420 constructors, so the timeout is the limit of a match -that wide, and a registered one. The extraction record +LexLeanPreserve.C….R0` (certificate A, of 3.7 MB as Lean text). The default limit `max_file_bytes` +itself allows about 250 constructors, since certificate A, recorded in the authoring notation, is about 3.9 MB at 240 and grows with the square of the arms, so for a match of more than about 250 constructors the limit, not the timeout, refuses the root, with `LLS8002` in the phase certificates (the Lean text was 4.1 MB at 420, and the data certificate is not smaller than the text for a wide match); the timeout is the limit of a match that wide, and a registered one. The extraction record of a named root nests with the arms of a match (two levels each), and is read without the 128-level recursion limit of the JSON reader on a thread with a large stack, so no number of arms makes a verified root fail on it. diff --git a/VERIFICATION.md b/VERIFICATION.md index 2190980d7..d1d04d2ff 100644 --- a/VERIFICATION.md +++ b/VERIFICATION.md @@ -1565,8 +1565,8 @@ production-coverage: Coverage.Colls.mapOps: the lower bounds Bounds { a: 51284, Planted: the cost of a pair of arms of certificate A raised from 180 to 500. Expected: the check that valid programs whose certificates fit are not refused -(a match on 420 constructors, a string of 450 000 characters, at the default -limits) refuses the match. +(a match on 240 constructors, a string of 450 000 characters, at the default +limits; certificate A is recorded in the notation, which is three times the Lean text of a wide match, so the match is 240 where it was 420) refuses the match. ```text Production.Main.r: lowering failed: Diagnostic { code: DiagnosticCode("LLS8002"), message: "root `Production.Main.r`: max_file_bytes exceeded in phase lowering: configured 4194304, its certificate A is at least 5524790 bytes (1265 program nodes)", primary: None, labels: [], notes: [], help: [], causes: [], detail: None } diff --git a/crates/conformance/src/stress.rs b/crates/conformance/src/stress.rs index 845fcf2b8..d49509da9 100644 --- a/crates/conformance/src/stress.rs +++ b/crates/conformance/src/stress.rs @@ -1256,7 +1256,7 @@ pub fn random_shapes(count: usize, seed: u64) -> Vec { /// The constructors of the widest enumeration whose certificate A, recorded in /// the authoring notation, fits under the default limit. -pub const FITTING_CONSTRUCTORS: usize = 420; +pub const FITTING_CONSTRUCTORS: usize = 240; /// A program that matches on an enumeration of `constructors` constructors, /// under the default limits. @@ -1266,8 +1266,8 @@ pub fn fitting_enumeration(constructors: usize) -> P { } /// Programs that are valid and whose certificates fit under the default -/// limits, with the default limits: none may be refused. A match on 420 -/// constructors (certificate A of 4.1 MB of the 4 MiB), a string literal of +/// limits, with the default limits: none may be refused. A match on 240 +/// constructors (certificate A of 3.9 MB of the 4 MiB, in the notation it is recorded in), a string literal of /// 450 000 characters (certificate B of 1.4 MB), and the largest scaling of /// each of [`mixed_shapes`] whose certificates all fit, which the caller /// finds by searching with [`mixed_lifted`]. From db18102ce2e80cb8015526ce7da57bd7e4e889cc Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 06:35:10 +0000 Subject: [PATCH 36/41] wip: the data behind the costs of certificate A (local, not pushed) Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- crates/conformance/src/preservation.rs | 70 ++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/crates/conformance/src/preservation.rs b/crates/conformance/src/preservation.rs index 092bbaee9..d5e68fbbb 100644 --- a/crates/conformance/src/preservation.rs +++ b/crates/conformance/src/preservation.rs @@ -3331,4 +3331,74 @@ mod dev { .join() .expect("the sizes"); } + + /// For many programs, the measure the early refusal reads and the weight + /// of certificate A as recorded, as `BOUNDDATA` lines of comma-separated + /// values: label, printed types, expressions, shapes, arm pairs, string + /// bytes, hex digits, notation bytes. + #[test] + #[ignore = "the data behind the costs of certificate A"] + fn dev_bound_data() { + std::thread::Builder::new() + .stack_size(1 << 30) + .spawn(|| { + let mut projects: Vec<(String, P)> = crate::stress::families(); + for arms in [2, 50, 100, 140, 180] { + projects.push((format!("wide match {arms}"), crate::stress::wide_match_default(arms))); + } + projects.extend(crate::stress::fitting()); + for scale in [4, 8, 16, 32, 64] { + for (index, shape) in crate::stress::mixed_shapes().into_iter().enumerate() { + projects.push(( + format!("mixed {index} at {scale}"), + crate::stress::mixed_lifted(shape.scaled(scale)), + )); + } + } + for (index, shape) in crate::stress::random_shapes(24, 0x5eed).into_iter().enumerate() { + projects.push((format!("random {index}"), crate::stress::mixed_lifted(shape))); + } + for name in ["production", "production-coverage", "models", "reasoning"] { + projects.push((format!("example {name}"), P::copy_example(name))); + } + for (label, project) in projects { + let checked = support::checked_project(&project); + let modules = linked_modules(&checked); + let limits = support::limits(&project); + for (index, root) in roots(&checked).expect("reports").iter().enumerate() { + let Ok(lowered) = + lower_root(&modules, &root.module, &root.name, root.report, &limits) + else { + continue; + }; + let module = format!("LexLeanPreserve.C{}.R{index}", "x".repeat(32)); + let Ok(a) = certificate( + &modules, + &root.module, + &root.name, + root.report, + &lowered, + &module, + UNBOUNDED, + ) else { + continue; + }; + let m = lexlean::production::lower::measure_program(&lowered.program); + eprintln!( + "BOUNDDATA {label} R{index},{},{},{},{},{},{},{}", + m.printed_types, + m.exprs, + m.shapes, + m.arm_pairs, + m.string_bytes, + m.hex_digits, + a.source.as_ref().map_or(0, String::len) + ); + } + } + }) + .expect("a thread") + .join() + .expect("the data"); + } } From 698c2fd33371c71181759e9189cfb3db1f60ce71 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 07:58:07 +0000 Subject: [PATCH 37/41] wip: the bound data takes mixed scalings (local, not pushed) Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- crates/conformance/src/preservation.rs | 51 +++++++++++++++++--------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/crates/conformance/src/preservation.rs b/crates/conformance/src/preservation.rs index d5e68fbbb..3ab8bd48d 100644 --- a/crates/conformance/src/preservation.rs +++ b/crates/conformance/src/preservation.rs @@ -3342,24 +3342,41 @@ mod dev { std::thread::Builder::new() .stack_size(1 << 30) .spawn(|| { - let mut projects: Vec<(String, P)> = crate::stress::families(); - for arms in [2, 50, 100, 140, 180] { - projects.push((format!("wide match {arms}"), crate::stress::wide_match_default(arms))); - } - projects.extend(crate::stress::fitting()); - for scale in [4, 8, 16, 32, 64] { - for (index, shape) in crate::stress::mixed_shapes().into_iter().enumerate() { - projects.push(( - format!("mixed {index} at {scale}"), - crate::stress::mixed_lifted(shape.scaled(scale)), - )); + let mut projects: Vec<(String, P)> = Vec::new(); + if let Ok(scales) = std::env::var("LEXLEAN_MIXED") { + let wanted: Vec = std::env::var("LEXLEAN_SHAPES") + .map(|list| list.split(',').filter_map(|v| v.trim().parse().ok()).collect()) + .unwrap_or_default(); + for scale in scales.split(',').filter_map(|v| v.trim().parse::().ok()) { + for (index, shape) in crate::stress::mixed_shapes().into_iter().enumerate() { + if wanted.is_empty() || wanted.contains(&index) { + projects.push(( + format!("mixed {index} at {scale}"), + crate::stress::mixed_lifted(shape.scaled(scale)), + )); + } + } + } + } else { + projects = crate::stress::families(); + for arms in [2, 50, 100, 140, 180] { + projects.push((format!("wide match {arms}"), crate::stress::wide_match_default(arms))); + } + projects.extend(crate::stress::fitting()); + for scale in [4, 8, 16, 32, 64] { + for (index, shape) in crate::stress::mixed_shapes().into_iter().enumerate() { + projects.push(( + format!("mixed {index} at {scale}"), + crate::stress::mixed_lifted(shape.scaled(scale)), + )); + } + } + for (index, shape) in crate::stress::random_shapes(24, 0x5eed).into_iter().enumerate() { + projects.push((format!("random {index}"), crate::stress::mixed_lifted(shape))); + } + for name in ["production", "production-coverage", "models", "reasoning"] { + projects.push((format!("example {name}"), P::copy_example(name))); } - } - for (index, shape) in crate::stress::random_shapes(24, 0x5eed).into_iter().enumerate() { - projects.push((format!("random {index}"), crate::stress::mixed_lifted(shape))); - } - for name in ["production", "production-coverage", "models", "reasoning"] { - projects.push((format!("example {name}"), P::copy_example(name))); } for (label, project) in projects { let checked = support::checked_project(&project); From 5855fe09f5c181f271a74ab929742cb0fd5af115 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 08:28:14 +0000 Subject: [PATCH 38/41] wip: the least costs of certificate A, refitted to the recorded notation (local, not pushed) Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- crates/lexlean/src/production/lower.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/crates/lexlean/src/production/lower.rs b/crates/lexlean/src/production/lower.rs index 9f81d4328..b9a09aa18 100644 --- a/crates/lexlean/src/production/lower.rs +++ b/crates/lexlean/src/production/lower.rs @@ -515,14 +515,22 @@ pub struct Costs { } /// What certificate A costs at the least. +/// +/// Certificate A is recorded as kernel data, whose hash-consing shares every +/// type and every repeated subterm, so a type node costs nothing here and the +/// costs are those of what is not shared. They are the largest costs, to the +/// sixteenth of a byte, that no program of the stress families, the mixed and +/// random shapes, the wide matches, and the four example corpora has a +/// certificate A smaller than, with five percent to spare (`conformance_sp_02` +/// checks every one of them). pub const COSTS_A: Costs = Costs { - base_bytes: 1080, - type_node: 284, - expr_node: 176, + base_bytes: 5400, + type_node: 0, + expr_node: 328, shape_node: 0, - arm_pair: 180, - string_byte: 7, - hex_digit: 19, + arm_pair: 1018, + string_byte: 15, + hex_digit: 433, }; /// What certificate B costs at the least. From 00f3a308d226154ea3c2910b069718abdeed1872 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 10:04:04 +0000 Subject: [PATCH 39/41] Lower the expression cost of the certificate A bound to fit the large mixed shape Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- crates/lexlean/src/production/lower.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lexlean/src/production/lower.rs b/crates/lexlean/src/production/lower.rs index b9a09aa18..b95ce61e4 100644 --- a/crates/lexlean/src/production/lower.rs +++ b/crates/lexlean/src/production/lower.rs @@ -526,7 +526,7 @@ pub struct Costs { pub const COSTS_A: Costs = Costs { base_bytes: 5400, type_node: 0, - expr_node: 328, + expr_node: 300, shape_node: 0, arm_pair: 1018, string_byte: 15, From dfe7b653301d94299840df1753c988381cbc518f Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 14:56:57 +0000 Subject: [PATCH 40/41] cargo fmt Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- crates/conformance/src/cases/preservation.rs | 27 +++++++-- crates/conformance/src/preservation.rs | 59 +++++++++++++++----- crates/lexlean/src/production/certificate.rs | 8 ++- 3 files changed, 73 insertions(+), 21 deletions(-) diff --git a/crates/conformance/src/cases/preservation.rs b/crates/conformance/src/cases/preservation.rs index 45d9172c9..086cbc66b 100644 --- a/crates/conformance/src/cases/preservation.rs +++ b/crates/conformance/src/cases/preservation.rs @@ -3055,7 +3055,10 @@ pub fn run(id: &str) { .expect("certificate A is data"); let module = &grove.certificate.module; let rows: Vec<&lexlean::ir::core::CoreDeclaration> = data.declarations.iter().collect(); - let has = |short: &str| rows.iter().any(|row| row.name.starts_with(&format!("{module}.{short}"))); + let has = |short: &str| { + rows.iter() + .any(|row| row.name.starts_with(&format!("{module}.{short}"))) + }; // The recursive group's validators are one mutual group of rows. assert!( rows.iter().any(|row| { @@ -3072,7 +3075,10 @@ pub fn run(id: &str) { "entry_refuses", "accepts", ] { - assert!(has(needed), "the recursive tree's certificate states `{needed}`"); + assert!( + has(needed), + "the recursive tree's certificate states `{needed}`" + ); } assert!( data.nodes.iter().any(|node| matches!(node, @@ -3896,9 +3902,15 @@ pub fn run(id: &str) { .into_iter() .flatten() { - if entry.path().extension().is_some_and(|extension| extension == "rs") { - sources.push(camino::Utf8PathBuf::from_path_buf(entry.path().to_path_buf()) - .expect("a UTF-8 path")); + if entry + .path() + .extension() + .is_some_and(|extension| extension == "rs") + { + sources.push( + camino::Utf8PathBuf::from_path_buf(entry.path().to_path_buf()) + .expect("a UTF-8 path"), + ); } } sources.push(production.join("certificate/data_a.rs")); @@ -3911,7 +3923,10 @@ pub fn run(id: &str) { ); scanned += 1; } - assert!(scanned > 10, "the scan reads the sources that build certificate A"); + assert!( + scanned > 10, + "the scan reads the sources that build certificate A" + ); // The scan is armed: a planted template is found. assert!( !templates(&format!("format!(\"theorem {}0 : …\")", "__rel_")).is_empty(), diff --git a/crates/conformance/src/preservation.rs b/crates/conformance/src/preservation.rs index 3ab8bd48d..ab8d942fc 100644 --- a/crates/conformance/src/preservation.rs +++ b/crates/conformance/src/preservation.rs @@ -197,7 +197,11 @@ pub fn halved(project: &P, certified: &[Certified]) -> Vec<(String, String, Vec< root.report, &lowered, &module, - held.certificate.source.as_ref().map_or(held.certificate.text.len(), String::len) as u64 / 2, + held.certificate + .source + .as_ref() + .map_or(held.certificate.text.len(), String::len) as u64 + / 2, )); let fallible = lexlean::calculus::rust::fallible_functions(&lowered.program) .expect("a valid program")[lowered.entry() as usize]; @@ -3183,7 +3187,12 @@ mod dev { if std::env::var("LEXLEAN_WIDE").is_ok() { projects = [2, 100, 101, 180] .iter() - .map(|arms| (format!("wide match {arms}"), crate::stress::wide_match_default(*arms))) + .map(|arms| { + ( + format!("wide match {arms}"), + crate::stress::wide_match_default(*arms), + ) + }) .collect(); } for (family, project) in projects { @@ -3216,7 +3225,10 @@ mod dev { limits.max_file_bytes ), Err(diagnostic) => { - eprintln!("FAMILY {family}: {}: {}", root.report.root, diagnostic.message); + eprintln!( + "FAMILY {family}: {}: {}", + root.report.root, diagnostic.message + ); } } } @@ -3283,8 +3295,9 @@ mod dev { let modules = linked_modules(&checked); let limits = support::limits(project); let root = roots(&checked).expect("reports").remove(0); - let lowered = lower_root(&modules, &root.module, &root.name, root.report, &limits) - .expect("lowering"); + let lowered = + lower_root(&modules, &root.module, &root.name, root.report, &limits) + .expect("lowering"); let module = format!("LexLeanPreserve.C{}.R0", "x".repeat(32)); certificate( &modules, @@ -3299,7 +3312,9 @@ mod dev { .map_err(|diagnostic| diagnostic.message) }; let limit = support::limits(&crate::stress::wide_match_default(2)).max_file_bytes; - let only: Option = std::env::var("LEXLEAN_SHAPE").ok().and_then(|v| v.parse().ok()); + let only: Option = std::env::var("LEXLEAN_SHAPE") + .ok() + .and_then(|v| v.parse().ok()); for (index, shape) in crate::stress::mixed_shapes().into_iter().enumerate() { if only.is_some_and(|only| only != index) { continue; @@ -3345,10 +3360,18 @@ mod dev { let mut projects: Vec<(String, P)> = Vec::new(); if let Ok(scales) = std::env::var("LEXLEAN_MIXED") { let wanted: Vec = std::env::var("LEXLEAN_SHAPES") - .map(|list| list.split(',').filter_map(|v| v.trim().parse().ok()).collect()) + .map(|list| { + list.split(',') + .filter_map(|v| v.trim().parse().ok()) + .collect() + }) .unwrap_or_default(); - for scale in scales.split(',').filter_map(|v| v.trim().parse::().ok()) { - for (index, shape) in crate::stress::mixed_shapes().into_iter().enumerate() { + for scale in scales + .split(',') + .filter_map(|v| v.trim().parse::().ok()) + { + for (index, shape) in crate::stress::mixed_shapes().into_iter().enumerate() + { if wanted.is_empty() || wanted.contains(&index) { projects.push(( format!("mixed {index} at {scale}"), @@ -3360,19 +3383,29 @@ mod dev { } else { projects = crate::stress::families(); for arms in [2, 50, 100, 140, 180] { - projects.push((format!("wide match {arms}"), crate::stress::wide_match_default(arms))); + projects.push(( + format!("wide match {arms}"), + crate::stress::wide_match_default(arms), + )); } projects.extend(crate::stress::fitting()); for scale in [4, 8, 16, 32, 64] { - for (index, shape) in crate::stress::mixed_shapes().into_iter().enumerate() { + for (index, shape) in crate::stress::mixed_shapes().into_iter().enumerate() + { projects.push(( format!("mixed {index} at {scale}"), crate::stress::mixed_lifted(shape.scaled(scale)), )); } } - for (index, shape) in crate::stress::random_shapes(24, 0x5eed).into_iter().enumerate() { - projects.push((format!("random {index}"), crate::stress::mixed_lifted(shape))); + for (index, shape) in crate::stress::random_shapes(24, 0x5eed) + .into_iter() + .enumerate() + { + projects.push(( + format!("random {index}"), + crate::stress::mixed_lifted(shape), + )); } for name in ["production", "production-coverage", "models", "reasoning"] { projects.push((format!("example {name}"), P::copy_example(name))); diff --git a/crates/lexlean/src/production/certificate.rs b/crates/lexlean/src/production/certificate.rs index 6f3ac8c7e..a2dd15462 100644 --- a/crates/lexlean/src/production/certificate.rs +++ b/crates/lexlean/src/production/certificate.rs @@ -407,7 +407,9 @@ pub fn certificate_e( .map_err(failure)?; let text = crate::backend::core::lean_text_legacy(&data, module) .map_err(|diagnostic| internal(diagnostic.message))?; - generator.within(text.len(), "certificate E").map_err(failure)?; + generator + .within(text.len(), "certificate E") + .map_err(failure)?; let source = record_source(module, &data, "E").map_err(internal)?; Ok(Certificate { module: module.to_owned(), @@ -547,7 +549,9 @@ pub fn certificate( // notation: the rendering to Lean text is the core backend's, and its size // is the backend's, not a property of the certificate. let source = record_source(module, &data, "A").map_err(internal)?; - generator.within(source.len(), "certificate A").map_err(failure)?; + generator + .within(source.len(), "certificate A") + .map_err(failure)?; let mut imports: Vec = modules .values() .map(|linked| linked.lean_module.to_owned()) From 7653ed1e2f0818d6629eb72c31ef9f97817b5c8c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 17:32:57 +0000 Subject: [PATCH 41/41] Plants: a lowering the generator refuses in lockstep counts as caught before the kernel Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01WG9wejRSGKo2jLG8BLxPs5 --- crates/conformance/src/cases/preservation.rs | 15 ++++ crates/conformance/src/preservation.rs | 74 ++++++++++++++++++-- 2 files changed, 85 insertions(+), 4 deletions(-) diff --git a/crates/conformance/src/cases/preservation.rs b/crates/conformance/src/cases/preservation.rs index 086cbc66b..d32a66050 100644 --- a/crates/conformance/src/cases/preservation.rs +++ b/crates/conformance/src/cases/preservation.rs @@ -86,6 +86,21 @@ fn assert_not_a_limit(what: &str, output: &str, text_len: usize) { /// template is instantiated, in the relation of a caller; not in the library, /// the environment, or an unrelated function. fn assert_targeted(plant: &preservation::Planted) { + if plant.refused.is_some() { + // The generator refused the mutated lowering while writing the + // certificate (it derives the lowering and the source in lockstep), + // which is earlier than the kernel; the function must still own a + // relation the mutation could have falsified. + assert!( + !plant.expected.is_empty(), + "{} {:?}: function {} ({:?}) has no relation to fail in", + plant.root, + plant.mutation, + plant.function, + plant.origin + ); + return; + } assert_not_a_limit( &format!("{} {:?}", plant.root, plant.mutation), &plant.rejection, diff --git a/crates/conformance/src/preservation.rs b/crates/conformance/src/preservation.rs index ab8d942fc..ed0a56ad7 100644 --- a/crates/conformance/src/preservation.rs +++ b/crates/conformance/src/preservation.rs @@ -482,7 +482,20 @@ fn parallel(items: &[T], job: impl Fn(&T) -> R + Sync) -> Vec< let Some(item) = items.get(index) else { break; }; - let result = job(item); + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| job(item))) + .unwrap_or_else(|payload| { + let message = payload + .downcast_ref::() + .cloned() + .or_else(|| { + payload + .downcast_ref::<&str>() + .map(|text| (*text).to_owned()) + }) + .unwrap_or_default(); + eprintln!("PARALLEL JOB PANIC: {message}"); + std::panic::resume_unwind(payload) + }); results.lock().expect("the results")[index] = Some(result); }); } @@ -1696,6 +1709,9 @@ pub struct Planted { /// what makes the failure targeted: the relation of the mutated /// function, not an unrelated one. pub declaration: Option, + /// The generator's refusal when the mutated lowering no longer follows the + /// source, so that no certificate was written for Lean to reject. + pub refused: Option, } /// The top-level declaration of `text` that the first error of Lean's @@ -1935,7 +1951,7 @@ pub fn plant(project: &P, mutations: &[Mutation]) -> Vec { .position(|job| *job == (mutation, root, function, ordinal)) .expect("the job"); let module = format!("LexLeanPreserve.Planted.P{index}"); - let certificate = certificate( + let certificate = match certificate( &modules, &roots[root].module, &roots[root].name, @@ -1943,8 +1959,32 @@ pub fn plant(project: &P, mutations: &[Mutation]) -> Vec { &lowered, &module, UNBOUNDED, - ) - .expect("the certificate is generated from the source"); + ) { + Ok(certificate) => certificate, + // Certificate A is derived from the source and the lowering in + // lockstep, so a lowering that no longer follows the source is + // refused while the certificate is written, before Lean sees it: + // the defect is caught earlier than by the kernel, and no + // declaration of a certificate exists to name. + Err(refusal) if refusal.message.starts_with("phase preservation:") => { + return Planted { + root: roots[root].report.root.clone(), + mutation, + function, + ordinal, + origin, + expected, + declaration: None, + text_len: 0, + rejection: refusal.message.clone(), + refused: Some(refusal.message), + }; + } + Err(error) => panic!( + "the certificate is generated from the source ({mutation:?} in function {function} at site {ordinal} of {}): {error:?}", + roots[root].report.root + ), + }; let path = directory.join(format!("P{index}.lean")); std::fs::write(&path, &certificate.text).expect("write"); let output = lean(scratch.path(), &[path.display().to_string()]); @@ -1963,6 +2003,7 @@ pub fn plant(project: &P, mutations: &[Mutation]) -> Vec { declaration: failing_declaration(&certificate.text, &rejection), text_len: certificate.text.len(), rejection, + refused: None, } }) } @@ -3347,6 +3388,31 @@ mod dev { .expect("the sizes"); } + /// Plant the lowering mutations in `production-coverage` and report the + /// outcome of each. + #[test] + #[ignore = "the planted defects of certificate A, run by hand"] + fn dev_plant() { + std::thread::Builder::new() + .stack_size(1 << 30) + .spawn(|| { + let planted = plant(&P::copy_example("production-coverage"), &Mutation::LOWERING); + for p in &planted { + println!( + "PLANT {:?} fn {} site {} rejected {} refused {}", + p.mutation, + p.function, + p.ordinal, + !p.rejection.is_empty(), + p.refused.is_some() + ); + } + }) + .expect("spawn") + .join() + .expect("plant"); + } + /// For many programs, the measure the early refusal reads and the weight /// of certificate A as recorded, as `BOUNDDATA` lines of comma-separated /// values: label, printed types, expressions, shapes, arm pairs, string