diff --git a/CHANGELOG.md b/CHANGELOG.md index cd87d4cb3..3f6772757 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,6 +112,23 @@ versions, and the entries below say what each tag does and does not claim. `leanchecker`; a mismatch is `LLI9001`, naming the record and both digests. Every CI job installs the same pinned toolchain, so this held by practice and is now checked. +- 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` (403 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 @@ -126,9 +143,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`). - Program equalities over all inputs (#117, SPEC.md §17.12, `PF-20`..`PF-23`, no new diagnostic code). Language 1.2 orders `Int` and every fixed-width diff --git a/CONFORMANCE.md b/CONFORMANCE.md index 0b7ba65a3..891c41b45 100644 --- a/CONFORMANCE.md +++ b/CONFORMANCE.md @@ -363,6 +363,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. The one Lean text a generated library module carries beside its data is the decoder of core module 2, which loads native modules. | | `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 b15e44d90..c17f04221 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,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 402 registered conformance IDs are implemented and pass; `just vv` runs clean from a checkout with the pinned toolchain installed. +All 403 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. @@ -174,7 +174,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 3d81e07df..a7bf92142 100644 --- a/SPEC.md +++ b/SPEC.md @@ -4797,7 +4797,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 @@ -4924,11 +4924,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. @@ -5429,7 +5428,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` @@ -5445,6 +5444,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 @@ -8221,7 +8258,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. @@ -9467,6 +9504,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. The one Lean text a generated library module carries beside its data is the decoder of core module 2, which loads native modules. | §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 | @@ -9553,7 +9591,7 @@ Every row below is normative, has honesty level `build`, and MUST be copied byte | `LM-03` | `language-models` | Text that is not valid UTF-8 is refused and never tokenized: an artifact whose bytes do not decode under the `utf8_lines` schema — invalid UTF-8, a CR, or a missing final LF — fails by its artifact rules before any byte is decoded, and a text offered to `encodeText` that is not valid UTF-8 is refused rather than transcoded. | §17.12 | | `LM-13` | `language-models` | The text laws a tokenizer elaborates through are LexLean source and not authored text: the module that states them is one language-1.2 native core module of the embedded `language/text-1.2` project, the project holds no file of Lean source, no independently authored Lean file anywhere in the release tree declares the `LexLeanText` namespace or lies under its module root, and the one-time migration input is absent; the registry lists only that module, the declarations and their exact axiom policies are read from its native data, which the compiler embeds and hashes into the language-1.2 semantics, and a module that declares a text component imports the generated module rather than splicing any Lean text. | §17.10, §17.12 | -**Total required capability IDs:** 402. +**Total required capability IDs:** 403. 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/VERIFICATION.md b/VERIFICATION.md index 77b079879..344127bf1 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/cases/preservation.rs b/crates/conformance/src/cases/preservation.rs index de8497958..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, @@ -385,14 +400,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 +463,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 +536,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 +578,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 @@ -1608,7 +1635,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 ); @@ -1643,13 +1675,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, {}", @@ -2323,7 +2350,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!( @@ -3036,21 +3063,44 @@ 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), + 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); @@ -3731,6 +3781,389 @@ 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}" + ); + } + } + // 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; + } + // 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/cases/repository.rs b/crates/conformance/src/cases/repository.rs index 1d4ad0389..a505e7574 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(), 402, "§31 has 402 rows"); + assert_eq!(table.len(), 403, "§31 has 403 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/src/preservation.rs b/crates/conformance/src/preservation.rs index 0d7776bca..ed0a56ad7 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.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]; @@ -478,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); }); } @@ -984,18 +1001,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") { @@ -1689,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 @@ -1773,7 +1796,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, @@ -1875,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, @@ -1883,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()]); @@ -1903,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, } }) } @@ -2841,3 +2942,579 @@ 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, {} of notation", + root.report.root, + certificate.text.len(), + certificate.source.as_ref().map_or(0, String::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() + ); + } + } + } + + /// 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 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] + #[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}")) + .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!( + 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 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\ + 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"); + 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(4000).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 + ); + } + + /// 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() { + 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() + .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(); + 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 + ); + } + } + } + } + } + + /// 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"); + } + + /// 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"); + } + + /// 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 + /// 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)> = 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 (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"); + } +} diff --git a/crates/conformance/src/stress.rs b/crates/conformance/src/stress.rs index 8517432dc..d49509da9 100644 --- a/crates/conformance/src/stress.rs +++ b/crates/conformance/src/stress.rs @@ -1254,9 +1254,20 @@ 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 = 240; + +/// 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 +/// 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`]. @@ -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(), diff --git a/crates/conformance/tests/conformance.rs b/crates/conformance/tests/conformance.rs index e769f05f6..ceab4bca9 100644 --- a/crates/conformance/tests/conformance.rs +++ b/crates/conformance/tests/conformance.rs @@ -1595,6 +1595,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/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/certificate.rs b/crates/lexlean/src/production/certificate.rs index 9ff90b7fc..a2dd15462 100644 --- a/crates/lexlean/src/production/certificate.rs +++ b/crates/lexlean/src/production/certificate.rs @@ -36,14 +36,14 @@ use std::collections::{BTreeMap, BTreeSet}; mod data; +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, }; @@ -70,7 +70,6 @@ pub const LIBRARY_MODULES: [&str; 7] = [ ]; const SYNTAX: &str = "LexLeanTarget.TargetSyntax"; -const NAT_BOUND: &str = "18446744073709551616"; /// One generated certificate module. #[derive(Debug, Clone, PartialEq, Eq)] @@ -207,14 +206,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 @@ -225,15 +216,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 @@ -306,45 +290,19 @@ impl Ctx { .extend(extra.into_iter().map(|(local, generic)| CLocal { local, generic, - companion: None, + has_companion: false, })); out } } -/// 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 { 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 { @@ -449,8 +407,10 @@ 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)?; - let source = record_source(module, &data).map_err(internal)?; + 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(), theorem: format!("{module}.root"), @@ -464,7 +424,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( @@ -472,7 +436,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 = crate::backend::core::lean_text_legacy(&data, module) + .map_err(|diagnostic| internal(diagnostic.message))?; + // 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(), "certificate A") .map_err(failure)?; - let text = from_the_root(&text).map_err(internal)?; - generator.within(text.len()).map_err(failure)?; let mut imports: Vec = modules .values() .map(|linked| linked.lean_module.to_owned()) @@ -592,68 +563,11 @@ pub fn certificate( denote: format!("{module}.denote"), text, imports, - data: None, - source: None, + data: Some(data), + source: Some(source), }) } -/// 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 @@ -1101,16 +1015,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 { @@ -1148,54 +1052,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( @@ -1238,568 +1094,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) - )), - } - } - - /// 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!( - "({}{} : {})", - runtime(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) - )); - } - }) - } -} - -/// A declaration of the runtime by its namespace and name. The runtime is -/// one set of imported modules (§17.12), not a copy inside each generated -/// module, so the name is the same at every site. -fn runtime(namespace: &str, operation: &str) -> String { - format!("{namespace}.{operation}") -} +impl Gen<'_> {} /// The runtime namespace and name a primitive is rendered with. const fn primitive_name(operation: SemanticPrimitive) -> (&'static str, &'static str) { @@ -1861,46 +1156,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) @@ -2167,231 +1422,8 @@ fn equal_suffix(ty: &SemanticType) -> Result<&'static str, String> { | SemanticType::Set { element: _ } => Err("an equality at a non-scalar type".to_owned()), } } - -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, - ) -> 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![ - runtime("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![ - runtime("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(), - runtime("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 @@ -2429,4926 +1461,1381 @@ 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> { + 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 )); } 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. + fn scope(locals: &[(Local, SemanticType)]) -> Vec { + locals + .iter() + .map(|(local, generic)| CLocal { + local: local.clone(), + generic: generic.clone(), + has_companion: function_type(&local.ty).is_some(), + }) + .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 = "LexLeanCollections".to_owned(); - 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)?; - 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")) + | SemanticDeclaration::Model { + name: _, + type_parameters: _, + contract: _, + realization: _, + evidence: _, + entry: _, + axioms: _, } - 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 + | SemanticDeclaration::Logic { + name: _, + type_parameters: _, + state: _, + relation: _, + invariant: _, + ranking: _, + axioms: _, } - }; - 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()); + | SemanticDeclaration::InferenceRule { + name: _, + type_parameters: _, + logic: _, + binding: _, + guard: _, + conclusion: _, + soundness: _, + progress: _, + executable: _, + axioms: _, } - 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 {} (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()); + | 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: _, + } + | SemanticDeclaration::Tokenizer { + name: _, + type_parameters: _, + scheme: _, + vocabulary: _, + axioms: _, + } + | SemanticDeclaration::ContextWindow { + name: _, + capacity: _, + overflow: _, + } + | SemanticDeclaration::TextGenerator { + name: _, + tokenizer: _, + window: _, + scorer: _, + max_tokens: _, + stop: _, + policy: _, } - None => {} + | SemanticDeclaration::TextClassifier { + name: _, + tokenizer: _, + model: _, + } => {} } - 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 - ) + group } -} -/// 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: _, - } - | SemanticDeclaration::Tokenizer { - name: _, - type_parameters: _, - scheme: _, - vocabulary: _, - axioms: _, - } - | SemanticDeclaration::ContextWindow { - name: _, - capacity: _, - overflow: _, - } - | SemanticDeclaration::TextGenerator { - name: _, - tokenizer: _, - window: _, - scorer: _, - max_tokens: _, - stop: _, - policy: _, - } - | SemanticDeclaration::TextClassifier { - name: _, - tokenizer: _, - model: _, - } => {} - } - } - 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: _, - } - | SemanticDeclaration::Tokenizer { - name: _, - type_parameters: _, - scheme: _, - vocabulary: _, - axioms: _, - } - | SemanticDeclaration::ContextWindow { - name: _, - capacity: _, - overflow: _, - } - | SemanticDeclaration::TextGenerator { - name: _, - tokenizer: _, - window: _, - scorer: _, - max_tokens: _, - stop: _, - policy: _, - } - | SemanticDeclaration::TextClassifier { - name: _, - tokenizer: _, - model: _, - }, - ) - | 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: _, - } - | SemanticDeclaration::Tokenizer { - name: _, - type_parameters: _, - scheme: _, - vocabulary: _, - axioms: _, - } - | SemanticDeclaration::ContextWindow { - name: _, - capacity: _, - overflow: _, - } - | SemanticDeclaration::TextGenerator { - name: _, - tokenizer: _, - window: _, - scorer: _, - max_tokens: _, - stop: _, - policy: _, - } - | SemanticDeclaration::TextClassifier { - name: _, - tokenizer: _, - model: _, - }, - ) - | 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) - )); - } - 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 { - name: _, - type_parameters, - parameters, - result: _, - recursive_argument: _, - body, - 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: _, - } - | SemanticDeclaration::Tokenizer { - name: _, - type_parameters: _, - scheme: _, - vocabulary: _, - axioms: _, - } - | SemanticDeclaration::ContextWindow { - name: _, - capacity: _, - overflow: _, - } - | SemanticDeclaration::TextGenerator { - name: _, - tokenizer: _, - window: _, - scorer: _, - max_tokens: _, - stop: _, - policy: _, - } - | SemanticDeclaration::TextClassifier { - name: _, - tokenizer: _, - model: _, - }, - ) - | 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()))); - } - 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) - } - - /// 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: _, } - 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::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: _, + } + | SemanticDeclaration::Tokenizer { + name: _, + type_parameters: _, + scheme: _, + vocabulary: _, + axioms: _, + } + | SemanticDeclaration::ContextWindow { + name: _, + capacity: _, + overflow: _, + } + | SemanticDeclaration::TextGenerator { + name: _, + tokenizer: _, + window: _, + scorer: _, + max_tokens: _, + stop: _, + policy: _, } - 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::TextClassifier { + name: _, + tokenizer: _, + model: _, + }, + ) + | 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: _, - } - | SemanticDeclaration::Tokenizer { - name: _, - type_parameters: _, - scheme: _, - vocabulary: _, - axioms: _, - } - | SemanticDeclaration::ContextWindow { - name: _, - capacity: _, - overflow: _, - } - | SemanticDeclaration::TextGenerator { - name: _, - tokenizer: _, - window: _, - scorer: _, - max_tokens: _, - stop: _, - policy: _, - } - | SemanticDeclaration::TextClassifier { - name: _, - tokenizer: _, - model: _, - }, - ) - | 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(), + }, + 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: _, + } + | SemanticDeclaration::Tokenizer { + name: _, + type_parameters: _, + scheme: _, + vocabulary: _, + axioms: _, + } + | SemanticDeclaration::ContextWindow { + name: _, + capacity: _, + overflow: _, + } + | SemanticDeclaration::TextGenerator { + name: _, + tokenizer: _, + window: _, + scorer: _, + max_tokens: _, + stop: _, + policy: _, + } + | SemanticDeclaration::TextClassifier { + name: _, + tokenizer: _, + model: _, + }, + ) + | 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)); + 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 = "LexLeanCollections".to_owned(); - 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), + 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 @@ -7363,126 +2850,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> { @@ -7760,64 +3248,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, _, kind) = checks[&index]; - let stated = self.stated(index, ty, 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 @@ -8029,432 +3459,4 @@ impl Gen<'_> { } Ok(()) } - - /// One validator's statements. - #[allow(clippy::too_many_lines)] - fn stated( - &self, - k: u64, - ty: &SemanticType, - 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 = "LexLeanCollections".to_owned(); - 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.rs b/crates/lexlean/src/production/certificate/data.rs index 661d7ef03..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, @@ -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/certificate/data_a.rs b/crates/lexlean/src/production/certificate/data_a.rs new file mode 100644 index 000000000..5718fcb80 --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a.rs @@ -0,0 +1,680 @@ +//! 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, + CoreSafety, CoreTransparency, CORE_SPEC_2, +}; +use crate::ir::semantic::{MemberRef, SemanticTerm, Step}; + +mod boundary; +mod calls; +mod clauses; +mod encoders; +mod lean; +mod matching; +mod prims; +mod prove; +mod recplan; +mod rows; +mod src; +mod structural; +mod templates; +mod wf; + +pub(super) const LIB: &str = "LexLeanPreservation"; + +/// 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, + }, + /// 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. +#[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, + /// 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 +/// `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, + /// 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 { + 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>, + /// `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. + sigs: HashMap, + /// The well-founded definition under derivation. + pub(super) wf: Option, +} + +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(); + for linked in gen.source.modules.values() { + 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, + ); + } + } + Self { + gen, + b, + name: name.to_owned(), + lemmas: HashMap::new(), + rows: Vec::new(), + row_axioms: BTreeMap::new(), + declared, + prog, + exprs: HashMap::new(), + sigs: HashMap::new(), + wf: None, + } + } + + // --- Names ------------------------------------------------------------ + + 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}")) + } + + /// 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(rest) = name.strip_prefix("LexLeanRuntime.") { + return Ok(runtime_axioms(rest)); + } + if let Some(rest) = name.strip_prefix("LexLeanCollections.") { + return Ok(collections_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 { + 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) + } + + /// 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 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" + )); + } + 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, + 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(); + 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, 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(row.group.clone().unwrap_or_else(|| 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, + 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(), + } +} + +/// 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/boundary.rs b/crates/lexlean/src/production/certificate/data_a/boundary.rs new file mode 100644 index 000000000..0f6769b29 --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/boundary.rs @@ -0,0 +1,2650 @@ +//! 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::{Pf, RowKind, Writer, EXPLICIT}; +use crate::calculus::{Expr, Function, Shape, Ty}; +use crate::ir::core::CoreLevel; +use crate::ir::semantic::SemanticType; +use crate::production::kernel::{Given, Term}; +use crate::production::source::Site; + +/// 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> { + self.gen.verify_boundary(parameters)?; + 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(()); + } + 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 { + return self.plain_document(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)?; + 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, 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 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)]); + 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 Ty::List { element } = parameter else { + return Err("a map validator over something other than a list".to_owned()); + }; + 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), + ("ν", 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 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( + "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 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( + "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 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), + ("β", 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 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), + ("ε", 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); + 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) -> Result<(), String> { + 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 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, + group: &Group, + 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(); + 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 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); + 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 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 + .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 expected = self.expected_validator(validator, function)?; + let body = match spec.arm { + Some(arm_index) => { + let Expr::Match { + ty: match_ty, + scrutinee, + arms, + } = &expected.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(); + 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 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; + 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, &expected.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)], + )?; + 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); + 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 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); + 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 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 { + "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: &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 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)?; + 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 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, + 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 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])?); + } + 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), + ], + )?; + self.forget_expression(&expected_body); + 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 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); + 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 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); + self.b.app(head, frees[checked[index].0]) + }; + 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 fls = 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_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); + 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)) + } +} 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..c295818ca --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/calls.rs @@ -0,0 +1,749 @@ +//! 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. +#[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). + pub(super) 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. + pub(super) 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, + 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 + .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), + 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 { + 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, + 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); + 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()), + 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(); + 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, + 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 + .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), + 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); + } + 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) + } + 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/clauses.rs b/crates/lexlean/src/production/certificate/data_a/clauses.rs new file mode 100644 index 000000000..9eb06c4b2 --- /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 _ = site; + Ok(format!("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/encoders.rs b/crates/lexlean/src/production/certificate/data_a/encoders.rs new file mode 100644 index 000000000..f051694ae --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/encoders.rs @@ -0,0 +1,437 @@ +//! 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::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; +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); + } + 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 + .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/lean.rs b/crates/lexlean/src/production/certificate/data_a/lean.rs new file mode 100644 index 000000000..b0e099a39 --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/lean.rs @@ -0,0 +1,846 @@ +//! 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::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]) + } + + // --- 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, 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..aabd2594e --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/matching.rs @@ -0,0 +1,697 @@ +//! 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, Step}; +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: &[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: &[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()), + 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)?; + 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"); + 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(); + 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 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)?; + 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 binders: Vec<(String, Term, Term)> = locals + .iter() + .enumerate() + .map(|(index, (local, _))| { + ( + super::super::identifier(&local.name), + types[index], + frees[index], + ) + }) + .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 + }; + 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 sorted_with = |minors: &Vec<(usize, Term)>| minors.clone(); + let (fits_terms, value_terms, proof_terms) = ( + 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 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 = 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)), + // 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 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); + 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`). 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, + match_ty: &crate::calculus::Ty, + scrutinee: &SemanticTerm, + branches: &[SemanticBranch], + arms: &'a [Arm], + scrutinee_expr: &'a Expr, + scope: &Scope, + ) -> Result { + 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()), + 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"); + 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()), + 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)) + .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/prims.rs b/crates/lexlean/src/production/certificate/data_a/prims.rs new file mode 100644 index 000000000..3998ab0cc --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/prims.rs @@ -0,0 +1,383 @@ +//! 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")?; + let index = self.b.app(index, alpha); + 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 _ = site; + Ok(self.b.konst(&format!("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, + 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(); + 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..58238adad --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/prove.rs @@ -0,0 +1,1351 @@ +//! 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; +use super::{LocalTerm, Pf, Scope, Writer, EXPLICIT}; +use crate::calculus::{Expr, Shape}; +use crate::ir::core::CoreLevel; +use crate::ir::semantic::{SemanticEdge, SemanticMapEntry, SemanticTerm, SemanticType, Step}; +use crate::production::kernel::{Given, Term}; +use crate::production::source::{Constructor, 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]) + } + + /// 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); + 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(&mut self, pf: Pf) -> Result { + Ok(self.rel_parts(pf.ty)?.0) + } + + /// The value of a derivation. + pub(super) fn value_of(&mut 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), + 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!( + "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, + 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(); + 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), + 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)?; + 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]) + } + 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)?; + 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, + 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!( + "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), + 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)?; + 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), + 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}")); + } + 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 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)], + &[ + 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(), + ), + 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) + } + 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(), + ), + 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( + Selection::And, + None, + 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(), + ), + 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( + Selection::Or, + None, + left, + &then_term, + right, + (c, t, e), + scope, + ) + } + SemanticTerm::Not { value } => { + let operand_exprs = match expr { + Expr::Prim { operands, .. } => operands, + 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)?; + 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(), + 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()), + 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'. + 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 { + "conv_second" + }; + self.apply( + name, + &[("p", p), ("env", env), ("a", a), ("b", b)], + &[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), + 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()), + } + } + + /// 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])), + 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()) + } + } + } + + /// 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 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)?; + } + 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), + 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}")); + 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, + 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)?; + 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)?; + 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 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, + 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 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 = 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) => { + 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 = 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 => { + 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, 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)], + &[ + 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), + ], + ) + } + + /// `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 = match hypothesis { + Some(hypothesis) => hypothesis, + None => self.b.free(), + }; + let proof = self.b.lam_over("_", EXPLICIT, ty, hypothesis, pf.proof); + let result = self.b.pi_over("_", EXPLICIT, ty, hypothesis, pf.ty); + Ok((proof, result)) + } + + // --- 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()), + 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(); + 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 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), ("x", name)], + &[ + Given::Proof(bound.proof, bound.ty), + Given::Proof(rest.proof, rest.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/certificate/data_a/recplan.rs b/crates/lexlean/src/production/certificate/data_a/recplan.rs new file mode 100644 index 000000000..1b3245db7 --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/recplan.rs @@ -0,0 +1,443 @@ +//! 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 new file mode 100644 index 000000000..0bba248d7 --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/rows.rs @@ -0,0 +1,612 @@ +//! 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, 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.b.konst("LexLeanRuntime.append"); + let instance = self.b.konst("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])); + 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 { + 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, + 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::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 structurally recursive outside a structural component" + )); + } + } + } + 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, + path: Vec::new(), + }; + 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, + path: Vec::new(), + }; + 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. + pub(super) 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)) + } + + /// `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, + }) + } + + /// 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..e13f05f7e --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/src.rs @@ -0,0 +1,1455 @@ +//! 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 + )) + } + + pub(super) 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 _ = site; + Ok(self.b.konst(&format!("{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 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)?; + 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"), + 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 { + 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(), + 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.math_int_instance(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", + 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)?); + } + 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", + )?); + } + 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")?; + 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(), + 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)?); + } + 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 self.collection_call(site, operation, types, args); + } + } + // `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")) + } + 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)) + } + } + } + + // --- 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()), + 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)?; + 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::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 + | 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::Function { + parameters: _, + result: _, + } => { + 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) + } + 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)?; + 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)?; + 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, + 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()), + 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)?; + 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()), + 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)?; + 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), + 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(); + 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 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)?; + 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)?; + 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) + } + + /// 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/structural.rs b/crates/lexlean/src/production/certificate/data_a/structural.rs new file mode 100644 index 000000000..2a8d4a14e --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/structural.rs @@ -0,0 +1,1084 @@ +//! 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::super::{CLocal, Ctx, Gen, Hypotheses, Recursion, Subject}; +use super::recplan::RecPlan; +use super::rows::{Binder, LocalSet, Sig}; +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, + 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, + 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 } = 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.has_companion { + 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, + body, + site, + type_parameters, + argument, + locals: clocals, + arg_position, + plan_position: 0, + fits_arg, + rel_arg, + 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![Vec::new(); plan.positions.len()]; + for (slot, member) in members.iter().enumerate() { + at_position[member.plan_position].push(slot); + } + let one = CoreLevel::Succ(Box::new(CoreLevel::Zero)); + 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); + 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(); members.len()]; + for position in 0..plan.positions.len() { + for ctor in 0..plan.positions[position].constructors.len() { + let (fits_minor, rel_minor, direct) = + self.structural_minors(&plan, &members, &at_position, position, ctor)?; + fits_minors.push(fits_minor); + rel_minors.push(rel_minor); + for (slot, term) in direct { + direct_minors[slot].push(term); + } + } + } + let mut params = Vec::new(); + for argument in &plan.params { + 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())); + 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 + .iter() + .enumerate() + .filter(|(slot, _)| *slot != member.fits_arg) + .map(|(_, binder)| binder.free) + .collect(); + 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 }, + row_ty, + 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)); + 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 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 + .iter() + .enumerate() + .filter(|(slot, _)| *slot != member.rel_arg) + .map(|(_, binder)| binder.free) + .collect(); + 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( + &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 (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[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( + &format!("__fits_{}._unsafe_rec", member.index), + RowKind::Partial, + row_ty, + value, + )?; + } + self.group_rows(&unsafe_names); + 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`. + 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"); + }; + 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`. + 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; + } + 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`. + pub(super) 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) + } + } + + /// 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 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_minors( + &mut self, + plan: &RecPlan, + members: &[Member<'a>], + at_position: &[Vec], + position: usize, + ctor: usize, + ) -> Result { + 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. + // 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, + members: &[Member<'a>], + at_position: &[Vec], + slot: usize, + ctor: usize, + 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. + 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 => {} + 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 + )); + } + } + 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)?; + if self.constructor_position(&constructor)? == 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)?; + // The other arguments of this minor are fresh. + let fresh = self.local_set(member.index, &member.locals)?; + let constructed = self.constructed( + &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, + has_companion: false, + }); + terms.push(LocalTerm { + value: *free, + companion: None, + }); + } + // The environment: the parameters bound to the encodings. + 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 { + 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, + path: Vec::new(), + }; + 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()), + 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) + .ok_or("a structural match lowered to fewer arms")?; + 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, + ..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 = self.prog; + let number = match scrutinee_expr { + Expr::Var { name } => *name, + 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( + "conv_var", + &[("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", 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 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")?; + 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)?; + // 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)) + .collect(); + 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), + (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, slots) in at_position.iter().enumerate().take(plan.positions.len()) { + for relation in [false, true] { + if slots.is_empty() { + continue; + } + let tys = self.component_types(members, slots, relation)?; + let frees: Vec = slots + .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 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)) + }) + }; + 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 { + out = writer + .b + .lam_over(&binder.name, EXPLICIT, binder.ty, binder.free, out); + } + } + out + }; + 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)?; + let head = b.konst(target); + Some(b.apps(head, args)) + }); + 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); + 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_inner, rel_inner, direct, constructed)) + } +} + +/// The component `index` of a packed value of types `tys`, built on the +/// builder alone. +pub(super) 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. +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"); + }; + 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]) + } +} + +/// 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/templates.rs b/crates/lexlean/src/production/certificate/data_a/templates.rs new file mode 100644 index 000000000..b371ac0e1 --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/templates.rs @@ -0,0 +1,919 @@ +//! 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::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: Template, + instance: &[SemanticType], + arguments: &[SemanticTerm], + types: &[SemanticType], + expr: &'a Expr, + scope: &Scope, + ) -> Result { + 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. + pub(super) fn less_than( + &mut self, + types: &[SemanticType], + arguments: &[SemanticTerm], + expr: &'a Expr, + scope: &Scope, + ) -> Result { + 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")) + } +} 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..68a90bca4 --- /dev/null +++ b/crates/lexlean/src/production/certificate/data_a/wf.rs @@ -0,0 +1,576 @@ +//! 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, SemanticTermination, SemanticType, Step, +}; +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, + 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(); + 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, + 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); + 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(()) + } +} 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..fea322ba7 --- /dev/null +++ b/crates/lexlean/src/production/kernel/ext.rs @@ -0,0 +1,1093 @@ +//! 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}; + +/// 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."; + +/// 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 { + /// 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; + 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 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 Rewrite<'_>) -> Term { + let mut memo = HashMap::new(); + self.rewrite_memo(term, rewrite, &mut memo) + } + + fn rewrite_memo( + &mut self, + term: Term, + rewrite: &mut Rewrite<'_>, + 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 { + 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() { + 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 > 4000 { + 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, 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, + free_too: bool, + ) -> bool { + if !seen.insert(term) { + return false; + } + match &self.nodes[term] { + 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) + } + CoreNode::Lambda { t, v, .. } | CoreNode::Forall { t, v, .. } => { + 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) + || 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, free_too) + } + } + } + + /// 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 { .. }, 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, .. }, + 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; + 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); + } + 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 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 + .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); + b.refl_proof(&self.name, position, domain, &mut self.solved)? + } + }; + 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_unsolved(term) || b.has_unsolved(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}"); + } +} diff --git a/crates/lexlean/src/production/lower.rs b/crates/lexlean/src/production/lower.rs index 72b99e1de..74a645266 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: 300, 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. diff --git a/crates/lexlean/src/production/preserve.rs b/crates/lexlean/src/production/preserve.rs index 65bbff4bd..38b92a626 100644 --- a/crates/lexlean/src/production/preserve.rs +++ b/crates/lexlean/src/production/preserve.rs @@ -510,7 +510,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(_)) { @@ -1703,7 +1714,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![ @@ -1717,7 +1731,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(), ), ), ( diff --git a/crates/lexlean/src/verify/mod.rs b/crates/lexlean/src/verify/mod.rs index 72837ba57..ac062e582 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/features/suites/preservation.feature b/features/suites/preservation.feature index 4178298b4..4c22925b0 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 658e7c96c..42ec38c0a 100644 --- a/model/ids.toml +++ b/model/ids.toml @@ -1916,6 +1916,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/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 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. diff --git a/xtask/src/spec_links.rs b/xtask/src/spec_links.rs index 07fcc3dd1..8e4e0aed1 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() != 402 { - return Err(format!("RP-07: the §31 table has {} rows, not 402", rows.len()).into()); + if rows.len() != 403 { + return Err(format!("RP-07: the §31 table has {} rows, not 403", 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.