Skip to main content

logicaffeine_kernel/interface/lean/
emit.rs

1//! LOGOS source for an imported Lean environment: one literate `.lg` document — prose headings,
2//! literate English forms where they express a declaration exactly, kernel vernacular for the rest —
3//! plus the correspondence between Lean and LOGOS names. The document is reparsed and rechecked in a
4//! fresh context before anyone may publish it.
5//!
6//! # What is written
7//!
8//! The target's dependency CLOSURE, every declaration the kernel checked for it: inductives with
9//! their constructors, definitions (the importer's sharing helpers and the recursors LOGOS derived
10//! included), theorems with their checked proofs, and axioms. Nothing is written as an assumption
11//! that the import checked as a proof, and nothing the import assumed is written as a proof.
12//!
13//! # How it is checked
14//!
15//! 1. While the document is built, every declaration is registered in a probe context the moment
16//!    it is written and compared with the imported declaration, so a failure names the declaration.
17//! 2. Before returning, the COMPLETE document is read by `Repl::execute_document` in a fresh
18//!    context, the number of commands is counted, every declaration is compared with the imported
19//!    one — kind, universe parameters, type, and body for a transparent definition, by strict
20//!    α-equivalence — and the fresh context is searched for anything the document declared that
21//!    the correspondence does not list.
22//! 3. [`check_statement`] compares the target's reparsed type with an independent reference
23//!    translation of the Lean type (competition rule R8).
24
25use super::{declined, rejected, DiagnosticCode, ImportedContext, Result, Verdict};
26use crate::interface::surface::{Name, Namespaces, Resolution};
27use crate::interface::{print_command, Command, Repl};
28use crate::{Context, Term};
29use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
30
31#[path = "emit_literate.rs"]
32mod literate;
33#[path = "emit_names.rs"]
34mod names;
35#[path = "emit_publish.rs"]
36mod publish;
37#[path = "emit_terms.rs"]
38mod terms;
39
40pub use names::LeanPart;
41pub use publish::{publish, publish_through, Published, DOCUMENT_EXTENSION, FAILURE_EXTENSION};
42
43/// Where a LOGOS declaration came from.
44#[derive(Clone, Debug, PartialEq, Eq)]
45pub enum Origin {
46    /// Written by the Lean author (or by Lean's elaborator, as an exported declaration).
47    Authored,
48    /// Introduced by the reader to preserve sharing inside the named authored declaration.
49    Helper { of: String },
50    /// Synthesized with no single Lean counterpart (for example, a derived recursor).
51    Synthesized,
52    /// The LOGOS prelude's own checked declaration of the same identity. It is not written in the
53    /// document; the fresh context already holds it, and verification compares it all the same.
54    Prelude,
55}
56
57/// What a declaration is, as the kernel holds it.
58#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
59pub enum DeclarationKind {
60    Inductive,
61    Constructor,
62    /// Transparent: its body unfolds.
63    Definition,
64    /// A checked proposition, Lean's `theorem`: its proof is written, checked by both kernels, and
65    /// unfolds exactly as Lean's kernel unfolds a theorem.
66    Theorem,
67    /// Checked, then sealed (Lean's `opaque`): its value is written and checked, and never unfolds.
68    Opaque,
69    /// Assumed: no proof exists.
70    Axiom,
71    /// One of the kernel's four certified quotient primitives (Lean's `init_quot`).
72    Quotient,
73}
74
75impl DeclarationKind {
76    pub fn as_str(self) -> &'static str {
77        match self {
78            DeclarationKind::Inductive => "inductive",
79            DeclarationKind::Constructor => "constructor",
80            DeclarationKind::Definition => "definition",
81            DeclarationKind::Theorem => "theorem",
82            DeclarationKind::Opaque => "opaque",
83            DeclarationKind::Axiom => "axiom",
84            DeclarationKind::Quotient => "quotient",
85        }
86    }
87}
88
89/// Which surface carries a declaration in the document.
90#[derive(Clone, Copy, Debug, PartialEq, Eq)]
91pub enum Form {
92    /// A literate English form that reparses to the identical kernel declaration.
93    Literate,
94    /// The kernel vernacular (`Definition`, `Inductive`, `Theorem`, `Opaque`, `Axiom`).
95    Vernacular,
96    /// Not written: the prelude's own declaration.
97    Prelude,
98}
99
100/// One Lean declaration and the LOGOS declaration that carries it.
101#[derive(Clone, Debug, PartialEq, Eq)]
102pub struct Correspondence {
103    /// The Lean name as Lean writes it; for a reader-introduced helper, the importer's own name; empty
104    /// for a constant the fresh LOGOS context derives itself (origin `Synthesized`), which Lean never
105    /// had.
106    pub lean: String,
107    /// The key the importer registered the declaration under; empty for a derived constant.
108    pub importer_key: String,
109    /// The absolute LOGOS name.
110    pub logos: String,
111    pub kind: DeclarationKind,
112    pub origin: Origin,
113    pub form: Form,
114    /// Universe parameters, in order: (importer's name, LOGOS name).
115    pub levels: Vec<(String, String)>,
116    /// The 1-based, inclusive line range of the LOGOS declaration in the document. `None` for a
117    /// prelude identity, which is not written. The Lean side carries no span: an export records
118    /// no source positions.
119    pub lines: Option<(usize, usize)>,
120}
121
122/// An emitted document.
123#[derive(Clone, Debug, PartialEq)]
124pub struct EmittedDocument {
125    /// The literate `.lg` source.
126    pub source: String,
127    /// The target's absolute LOGOS name.
128    pub target: String,
129    pub correspondence: Vec<Correspondence>,
130    /// Declarations whose literate readable form was omitted (the vernacular form carries them),
131    /// each with the reason; nothing is dropped silently.
132    pub omissions: Vec<String>,
133    /// The axioms the document assumes, by LOGOS name.
134    pub assumptions: Vec<String>,
135    /// What the import knew that the document does not carry, each as (category, detail) — read from
136    /// the import's metadata (binder annotations), so it is NOT part of the document's bytes: a replay
137    /// that rebuilds the environment from its certificate writes the identical source without it.
138    pub losses: Vec<(String, String)>,
139    /// What every declaration must reparse to, in LOGOS names.
140    expected: Vec<Expected>,
141}
142
143/// Why an emitted document does not reparse to the imported declarations. Every variant names the
144/// LOGOS declaration it concerns.
145#[derive(Clone, Debug, PartialEq, Eq)]
146pub enum EmissionFault {
147    /// The fresh context refused the document.
148    Reparse { message: String },
149    /// The document holds a different number of declaration commands than it should.
150    CommandCount { expected: usize, found: usize },
151    /// A declaration the document must declare is absent.
152    Missing { logos: String },
153    /// A declaration was registered as a different kind (a theorem that unfolds, an axiom with a
154    /// proof, …).
155    Kind {
156        logos: String,
157        expected: DeclarationKind,
158    },
159    /// A declaration's universe parameters differ.
160    Levels {
161        logos: String,
162        expected: Vec<String>,
163        found: Vec<String>,
164    },
165    /// A declaration's type differs.
166    Type { logos: String },
167    /// A transparent definition's body differs.
168    Body { logos: String },
169    /// An inductive's parameter count or constructors differ.
170    Constructors { logos: String },
171    /// The document declared something its correspondence does not list.
172    Undeclared { logos: String },
173    /// The target's reparsed statement differs from the reference translation of the Lean type.
174    Statement { logos: String },
175    /// A reference term names a constant the correspondence does not map.
176    Unmapped { name: String },
177    /// The independent re-checker did not confirm a reparsed definition at its declared type.
178    Recheck { logos: String },
179    /// An inductive gained, lost or changed its structure η (its projections).
180    Structure { logos: String },
181}
182
183impl std::fmt::Display for EmissionFault {
184    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185        match self {
186            EmissionFault::Reparse { message } => {
187                write!(f, "the fresh context refused the document: {message}")
188            }
189            EmissionFault::CommandCount { expected, found } => {
190                write!(
191                    f,
192                    "the document holds {found} declaration commands where {expected} were written"
193                )
194            }
195            EmissionFault::Missing { logos } => {
196                write!(f, "`{logos}` is not declared after reparsing")
197            }
198            EmissionFault::Kind { logos, expected } => {
199                write!(f, "`{logos}` did not reparse as a {}", expected.as_str())
200            }
201            EmissionFault::Levels {
202                logos,
203                expected,
204                found,
205            } => {
206                write!(
207                    f,
208                    "`{logos}` reparsed with universe parameters {found:?}, not {expected:?}"
209                )
210            }
211            EmissionFault::Type { logos } => write!(f, "`{logos}` reparsed with a different type"),
212            EmissionFault::Body { logos } => write!(f, "`{logos}` reparsed with a different body"),
213            EmissionFault::Constructors { logos } => {
214                write!(
215                    f,
216                    "`{logos}` reparsed with different parameters or constructors"
217                )
218            }
219            EmissionFault::Undeclared { logos } => {
220                write!(
221                    f,
222                    "the document declares `{logos}`, which its correspondence does not list"
223                )
224            }
225            EmissionFault::Statement { logos } => {
226                write!(
227                    f,
228                    "`{logos}` does not state the reference translation of its Lean type"
229                )
230            }
231            EmissionFault::Unmapped { name } => write!(f, "`{name}` has no LOGOS correspondent"),
232            EmissionFault::Structure { logos } => {
233                write!(
234                    f,
235                    "`{logos}` did not reparse with the imported structure η and projections"
236                )
237            }
238            EmissionFault::Recheck { logos } => {
239                write!(
240                    f,
241                    "the independent re-checker does not confirm `{logos}` at its declared type"
242                )
243            }
244        }
245    }
246}
247
248/// The largest document the emitter writes.
249pub const MAX_SOURCE_BYTES: usize = 64 * 1024 * 1024;
250
251/// The dependency closure of `target`, as a LOGOS document.
252///
253/// A theorem's proof is carried only when the import retained it (`import_retaining_closure`
254/// retains the whole closure); an opaque theorem in the closure whose proof was not retained is
255/// refused, never written as an axiom.
256pub fn emit_document(imported: &ImportedContext, target: &str) -> Result<EmittedDocument> {
257    let proofs = |key: &str| imported.retained(key).map(|(_, body)| body.clone());
258    emit_context(imported, &proofs, target)
259}
260
261/// [`emit_document`] over any checked context whose constants carry the importer's key scheme.
262/// `proofs` hands back the checked proof of an opaque theorem, which a context does not keep.
263pub fn emit_context(
264    ctx: &Context,
265    proofs: &(dyn Fn(&str) -> Option<Term> + Sync),
266    target: &str,
267) -> Result<EmittedDocument> {
268    let closure = Closure::of(ctx, proofs, target)?;
269    let depth = closure.depth();
270    let rungs = depth
271        .saturating_mul(3)
272        .saturating_add(crate::term_store::INLINE_DEPTH);
273    let stack = crate::term_store::stack_bytes_for_depth(rungs);
274    if stack > 8 * 1024 * 1024 * 1024 {
275        return Err(declined(
276            DiagnosticCode::ResourceLimit,
277            format!(
278                "a term of depth {depth} is deeper than LOGOS source emission reserves stack for"
279            ),
280        ));
281    }
282    std::thread::scope(|scope| {
283        std::thread::Builder::new()
284            .name("lean-emit".into())
285            .stack_size(stack)
286            .spawn_scoped(scope, || Emitter::new(ctx, &closure)?.run())
287            .map_err(|e| {
288                declined(
289                    DiagnosticCode::Infrastructure,
290                    format!("cannot reserve emitter stack: {e}"),
291                )
292            })?
293            .join()
294            .unwrap_or_else(|p| std::panic::resume_unwind(p))
295    })
296}
297
298/// Reparse `document.source` in a fresh context and compare every declaration with what it must
299/// be. Returns the checked context.
300pub fn verify(document: &EmittedDocument) -> std::result::Result<Repl, EmissionFault> {
301    let depth = document
302        .expected
303        .iter()
304        .flat_map(Expected::terms)
305        .map(crate::term_store::term_depth)
306        .max()
307        .unwrap_or(0);
308    let rungs = depth
309        .saturating_mul(3)
310        .saturating_add(crate::term_store::INLINE_DEPTH);
311    let stack = crate::term_store::stack_bytes_for_depth(rungs);
312    std::thread::scope(|scope| {
313        std::thread::Builder::new()
314            .name("lean-emit-verify".into())
315            .stack_size(stack)
316            .spawn_scoped(scope, || {
317                verify_on_this_stack(document, true).map(|(repl, _)| repl)
318            })
319            .map_err(|e| EmissionFault::Reparse {
320                message: format!("cannot reserve verifier stack: {e}"),
321            })?
322            .join()
323            .unwrap_or_else(|p| std::panic::resume_unwind(p))
324    })
325}
326
327/// Rule R8: the target's REPARSED statement against `reference`, an independent translation of the
328/// Lean declaration (`reference::reference_declaration`) in the importer's names. The reference is
329/// carried into LOGOS names through the correspondence alone — the emitter's own terms play no part —
330/// and must name the target, bind the same universe parameters in the same order, and state the same
331/// type up to α.
332pub fn check_statement(
333    document: &EmittedDocument,
334    reference: &super::reference::ReferenceDeclaration,
335) -> std::result::Result<(), EmissionFault> {
336    let repl = verify(document)?;
337    let logos = document.target.clone();
338    let target = document
339        .correspondence
340        .iter()
341        .find(|c| c.logos == logos)
342        .ok_or_else(|| EmissionFault::Missing {
343            logos: logos.clone(),
344        })?;
345    if reference.name != target.importer_key {
346        return Err(EmissionFault::Statement { logos });
347    }
348    let levels: HashMap<String, String> = target.levels.iter().cloned().collect();
349    let reference_levels = reference
350        .level_params
351        .iter()
352        .map(|l| {
353            levels
354                .get(l)
355                .cloned()
356                .ok_or_else(|| EmissionFault::Unmapped { name: l.clone() })
357        })
358        .collect::<std::result::Result<Vec<_>, _>>()?;
359    let globals: HashMap<&str, &str> = document
360        .correspondence
361        .iter()
362        .map(|c| (c.importer_key.as_str(), c.logos.as_str()))
363        .collect();
364    let lookup = |k: &str| globals.get(k).map(|s| s.to_string());
365    let renamed = terms::rename(&reference.ty, &lookup, &levels).map_err(|fault| match fault {
366        terms::TermFault::UnmappedGlobal(name) | terms::TermFault::UnboundLevel(name) => {
367            EmissionFault::Unmapped { name }
368        }
369        terms::TermFault::FreeVariable(_) | terms::TermFault::Hole => EmissionFault::Statement {
370            logos: logos.clone(),
371        },
372    })?;
373    let (found_levels, found) =
374        header(repl.context(), &logos).ok_or_else(|| EmissionFault::Missing {
375            logos: logos.clone(),
376        })?;
377    if found_levels != reference_levels {
378        return Err(EmissionFault::Levels {
379            logos,
380            expected: reference_levels,
381            found: found_levels,
382        });
383    }
384    if terms::alpha_eq(&renamed, &found) {
385        Ok(())
386    } else {
387        Err(EmissionFault::Statement { logos })
388    }
389}
390
391// ─────────────────────────────────────────────────────────────────────────────────────────────
392// The closure
393// ─────────────────────────────────────────────────────────────────────────────────────────────
394
395/// One unit the document declares (or, for a prelude identity, compares).
396#[derive(Clone, Debug)]
397enum Decl {
398    /// An inductive block the prelude already holds under the same identity (`Nat`).
399    Prelude { members: Vec<String> },
400    /// An inductive block, members in block order.
401    Inductive { members: Vec<String> },
402    Definition {
403        key: String,
404        levels: Vec<String>,
405        ty: Term,
406        body: Term,
407    },
408    /// A checked body with dependency evidence: a Lean `theorem` (`transparent`, it unfolds) or a Lean
409    /// `opaque` (sealed; its value is only in the retained proofs).
410    Theorem {
411        key: String,
412        levels: Vec<String>,
413        ty: Term,
414        proof: Term,
415        transparent: bool,
416    },
417    Axiom {
418        key: String,
419        levels: Vec<String>,
420        ty: Term,
421    },
422    /// A kernel quotient primitive; `equality` is the equality constant `Lift` states respect with.
423    Quotient {
424        key: String,
425        kind: crate::quotient::Kind,
426        levels: Vec<String>,
427        ty: Term,
428        equality: Option<String>,
429    },
430}
431
432impl Decl {
433    /// The key a unit is ordered and reported by.
434    fn key(&self) -> &str {
435        match self {
436            Decl::Prelude { members } | Decl::Inductive { members } => &members[0],
437            Decl::Definition { key, .. }
438            | Decl::Theorem { key, .. }
439            | Decl::Axiom { key, .. }
440            | Decl::Quotient { key, .. } => key,
441        }
442    }
443}
444
445/// The keys the fresh context holds under the prelude's identity.
446const PRELUDE_IDENTITIES: [&str; 3] = ["Nat", "Zero", "Succ"];
447
448const HELPER_PREFIX: &str = "arena.internal.dag.";
449
450struct Closure {
451    decls: Vec<Decl>,
452    /// Every key the closure declares → its unit.
453    unit_of: BTreeMap<String, usize>,
454    /// Per unit, the units it references.
455    deps: Vec<BTreeSet<usize>>,
456    /// The unit declaring the target.
457    target: usize,
458    /// The requested key itself (a constructor's, when a constructor was asked for).
459    target_key: String,
460}
461
462impl Closure {
463    fn of(
464        ctx: &Context,
465        proofs: &(dyn Fn(&str) -> Option<Term> + Sync),
466        target: &str,
467    ) -> Result<Closure> {
468        let mut decls: Vec<Decl> = Vec::new();
469        let mut unit_of: BTreeMap<String, usize> = BTreeMap::new();
470        let mut refs: Vec<BTreeSet<String>> = Vec::new();
471        let mut work: Vec<(String, Option<String>)> = vec![(target.to_string(), None)];
472        while let Some((key, referrer)) = work.pop() {
473            if unit_of.contains_key(&key) {
474                continue;
475            }
476            let decl = classify(ctx, proofs, &key).map_err(|verdict| match referrer {
477                Some(from) => verdict_with_prefix(verdict, format!("`{from}` refers to `{key}`")),
478                None => verdict,
479            })?;
480            let index = decls.len();
481            let mut mentioned = BTreeSet::new();
482            let covered = unit_keys(ctx, &decl);
483            match &decl {
484                Decl::Prelude { .. } => {}
485                Decl::Inductive { members } => {
486                    for member in members {
487                        if let Some(arity) = ctx.get_global(member) {
488                            terms::references(arity, &mut mentioned);
489                        }
490                        for (_, ty) in ctx.get_constructors(member) {
491                            terms::references(ty, &mut mentioned);
492                        }
493                        for projection in projections_of(ctx, member) {
494                            if let Some((_, ty, body)) = definition_payload(ctx, &projection) {
495                                terms::references(&ty, &mut mentioned);
496                                terms::references(&body, &mut mentioned);
497                            }
498                        }
499                    }
500                }
501                Decl::Definition { ty, body, .. } => {
502                    terms::references(ty, &mut mentioned);
503                    terms::references(body, &mut mentioned);
504                }
505                Decl::Theorem { ty, proof, .. } => {
506                    terms::references(ty, &mut mentioned);
507                    terms::references(proof, &mut mentioned);
508                }
509                Decl::Axiom { ty, .. } | Decl::Quotient { ty, .. } => {
510                    terms::references(ty, &mut mentioned)
511                }
512            }
513            for k in &covered {
514                unit_of.insert(k.clone(), index);
515            }
516            for k in &mentioned {
517                if !covered.contains(k) {
518                    work.push((k.clone(), Some(display_key(&key))));
519                }
520            }
521            mentioned.retain(|k| !covered.contains(k));
522            refs.push(mentioned);
523            decls.push(decl);
524        }
525        let deps = refs
526            .iter()
527            .map(|mentioned| mentioned.iter().map(|k| unit_of[k]).collect())
528            .collect();
529        Ok(Closure {
530            target: unit_of[target],
531            target_key: target.to_string(),
532            decls,
533            unit_of,
534            deps,
535        })
536    }
537
538    /// The deepest term the document will hold.
539    fn depth(&self) -> usize {
540        self.decls
541            .iter()
542            .flat_map(|d| match d {
543                Decl::Definition { ty, body, .. } => vec![ty, body],
544                Decl::Theorem { ty, proof, .. } => vec![ty, proof],
545                Decl::Axiom { ty, .. } | Decl::Quotient { ty, .. } => vec![ty],
546                Decl::Prelude { .. } | Decl::Inductive { .. } => vec![],
547            })
548            .map(crate::term_store::term_depth)
549            .max()
550            .unwrap_or(0)
551    }
552}
553
554fn verdict_with_prefix(verdict: Verdict, prefix: String) -> Verdict {
555    verdict.located(prefix)
556}
557
558/// A key as a reader recognizes it: the Lean name when there is one.
559fn display_key(key: &str) -> String {
560    names::lean_name(key).map_or_else(|| key.to_string(), |p| names::lean_display(&p))
561}
562
563/// Every key one unit declares.
564fn unit_keys(ctx: &Context, decl: &Decl) -> BTreeSet<String> {
565    match decl {
566        Decl::Prelude { members } | Decl::Inductive { members } => {
567            let mut keys: BTreeSet<String> = members.iter().cloned().collect();
568            for member in members {
569                keys.extend(
570                    ctx.get_constructors(member)
571                        .into_iter()
572                        .map(|(c, _)| c.to_string()),
573                );
574                keys.extend(projections_of(ctx, member));
575            }
576            keys
577        }
578        other => BTreeSet::from([other.key().to_string()]),
579    }
580}
581
582/// The projections of `inductive` when the import registered it as a structure (Lean's
583/// `toCtorWhenStruct`: definitional η) — they belong to the inductive's unit, written with it and before
584/// anything that relies on η.
585fn projections_of(ctx: &Context, inductive: &str) -> Vec<String> {
586    ctx.struct_info(inductive)
587        .map(|info| info.projections.clone())
588        .unwrap_or_default()
589}
590
591/// What the context holds under `key`.
592fn classify(
593    ctx: &Context,
594    proofs: &(dyn Fn(&str) -> Option<Term> + Sync),
595    key: &str,
596) -> Result<Decl> {
597    if let Some(kind) = quotient_kind(key) {
598        let (levels, ty) = header(ctx, key).ok_or_else(|| {
599            rejected(
600                DiagnosticCode::MissingDeclaration,
601                format!(
602                    "the quotient primitive `{}` is not installed",
603                    display_key(key)
604                ),
605            )
606        })?;
607        // `Lift`'s respect hypothesis is stated with an equality, the one constant its signature names
608        // besides the quotient itself.
609        let equality = if kind == crate::quotient::Kind::Lift {
610            let mut names = BTreeSet::new();
611            terms::references(&ty, &mut names);
612            match names.into_iter().find(|n| quotient_kind(n).is_none()) {
613                Some(eq) => Some(eq),
614                None => {
615                    return Err(rejected(
616                        DiagnosticCode::MalformedExport,
617                        "the quotient lift names no equality",
618                    ))
619                }
620            }
621        } else {
622            None
623        };
624        return Ok(Decl::Quotient {
625            key: key.to_string(),
626            kind,
627            levels,
628            ty,
629            equality,
630        });
631    }
632    if PRELUDE_IDENTITIES.contains(&key) {
633        return Ok(Decl::Prelude {
634            members: vec!["Nat".to_string()],
635        });
636    }
637    if let Some(inductive) = ctx.constructor_inductive(key) {
638        return classify(ctx, proofs, &inductive.to_string());
639    }
640    if ctx.is_inductive(key) {
641        let members = ctx
642            .mutual_block_of(key)
643            .map(<[String]>::to_vec)
644            .unwrap_or_else(|| vec![key.to_string()]);
645        return Ok(Decl::Inductive { members });
646    }
647    if ctx.checked_opaque_dependencies(key).is_some() {
648        if let Some((levels, ty, proof)) = definition_payload(ctx, key) {
649            return Ok(Decl::Theorem {
650                key: key.to_string(),
651                levels,
652                ty,
653                proof,
654                transparent: true,
655            });
656        }
657    }
658    if let Some((levels, ty, body)) = ctx.get_universe_poly(key) {
659        return Ok(Decl::Definition {
660            key: key.to_string(),
661            levels: levels.clone(),
662            ty: ty.clone(),
663            body: body.clone(),
664        });
665    }
666    if let (Some(ty), Some(body)) = (ctx.get_definition_type(key), ctx.get_definition_body(key)) {
667        return Ok(Decl::Definition {
668            key: key.to_string(),
669            levels: Vec::new(),
670            ty: ty.clone(),
671            body: body.clone(),
672        });
673    }
674    if let Some((levels, ty)) = header(ctx, key) {
675        if ctx.checked_opaque_dependencies(key).is_some() {
676            let proof = proofs(key).ok_or_else(|| {
677                declined(
678                    DiagnosticCode::UnsupportedFeature,
679                    format!(
680                        "the checked value of the opaque declaration `{}` was not retained by the \
681                         import, and a checked declaration is never written as an assumption",
682                        display_key(key)
683                    ),
684                )
685            })?;
686            return Ok(Decl::Theorem {
687                key: key.to_string(),
688                levels,
689                ty,
690                proof,
691                transparent: false,
692            });
693        }
694        return Ok(Decl::Axiom {
695            key: key.to_string(),
696            levels,
697            ty,
698        });
699    }
700    if let Some((_, blocker)) = ctx
701        .pending_obligations()
702        .into_iter()
703        .find(|(pending, _)| *pending == key)
704    {
705        return Err(rejected(
706            DiagnosticCode::MissingDeclaration,
707            format!(
708                "`{}` waits for `{}`, which is not declared: an unresolved forward obligation, not a checked declaration",
709                display_key(key),
710                display_key(blocker)
711            ),
712        ));
713    }
714    Err(rejected(
715        DiagnosticCode::MissingDeclaration,
716        format!(
717            "`{}` is not declared in the imported environment",
718            display_key(key)
719        ),
720    ))
721}
722
723/// The quotient primitive a kernel key names.
724fn quotient_kind(key: &str) -> Option<crate::quotient::Kind> {
725    use crate::quotient::Kind;
726    [Kind::Type, Kind::Constructor, Kind::Lift, Kind::Induction]
727        .into_iter()
728        .find(|k| k.name() == key)
729}
730
731/// A declaration's level telescope and type, however it was registered.
732fn header(ctx: &Context, key: &str) -> Option<(Vec<String>, Term)> {
733    if let Some((levels, ty)) = ctx.poly_declaration(key) {
734        return Some((levels.to_vec(), ty.clone()));
735    }
736    ctx.get_global(key).map(|ty| (Vec::new(), ty.clone()))
737}
738
739/// A transparent definition's level telescope, type and body, however it was registered.
740fn definition_payload(ctx: &Context, key: &str) -> Option<(Vec<String>, Term, Term)> {
741    if let Some((levels, ty, body)) = ctx.get_universe_poly(key) {
742        return Some((levels.clone(), ty.clone(), body.clone()));
743    }
744    Some((
745        Vec::new(),
746        ctx.get_definition_type(key)?.clone(),
747        ctx.get_definition_body(key)?.clone(),
748    ))
749}
750
751// ─────────────────────────────────────────────────────────────────────────────────────────────
752// What each declaration must reparse to
753// ─────────────────────────────────────────────────────────────────────────────────────────────
754
755#[derive(Clone, Debug, PartialEq)]
756struct ExpectedInductive {
757    logos: String,
758    levels: Vec<String>,
759    arity: Term,
760    params: usize,
761    constructors: Vec<(String, Term)>,
762    /// For a structure (η), its projections: LOGOS name, type, body — in field order.
763    structure: Option<Vec<(String, Term, Term)>>,
764}
765
766#[derive(Clone, Debug, PartialEq)]
767enum Expected {
768    Inductive {
769        members: Vec<ExpectedInductive>,
770        written: bool,
771    },
772    Definition {
773        logos: String,
774        levels: Vec<String>,
775        ty: Term,
776        body: Term,
777    },
778    /// `body` is the proof a transparent theorem unfolds to; `None` for a sealed `Opaque`.
779    Theorem {
780        logos: String,
781        levels: Vec<String>,
782        ty: Term,
783        body: Option<Term>,
784    },
785    Axiom {
786        logos: String,
787        levels: Vec<String>,
788        ty: Term,
789    },
790    Quotient {
791        logos: String,
792        levels: Vec<String>,
793        ty: Term,
794    },
795}
796
797impl Expected {
798    fn terms(&self) -> Vec<&Term> {
799        match self {
800            Expected::Inductive { members, .. } => members
801                .iter()
802                .flat_map(|m| {
803                    std::iter::once(&m.arity)
804                        .chain(m.constructors.iter().map(|(_, t)| t))
805                        .chain(
806                            m.structure
807                                .iter()
808                                .flatten()
809                                .flat_map(|(_, ty, body)| [ty, body]),
810                        )
811                })
812                .collect(),
813            Expected::Definition { ty, body, .. } => vec![ty, body],
814            Expected::Theorem {
815                ty,
816                body: Some(body),
817                ..
818            } => vec![ty, body],
819            Expected::Theorem { ty, .. }
820            | Expected::Axiom { ty, .. }
821            | Expected::Quotient { ty, .. } => vec![ty],
822        }
823    }
824
825    /// How many declaration commands the document spends on this unit.
826    fn commands(&self) -> usize {
827        match self {
828            // The block, then for each structure its projection definitions and its `Structure`.
829            Expected::Inductive {
830                written: true,
831                members,
832            } => {
833                1 + members
834                    .iter()
835                    .filter_map(|m| m.structure.as_ref())
836                    .map(|p| p.len() + 1)
837                    .sum::<usize>()
838            }
839            Expected::Inductive { written: false, .. } => 0,
840            _ => 1,
841        }
842    }
843
844    /// Every LOGOS key this unit declares.
845    fn logos_keys(&self) -> Vec<&str> {
846        match self {
847            Expected::Inductive { members, .. } => members
848                .iter()
849                .flat_map(|m| {
850                    std::iter::once(m.logos.as_str())
851                        .chain(m.constructors.iter().map(|(c, _)| c.as_str()))
852                        .chain(m.structure.iter().flatten().map(|(p, _, _)| p.as_str()))
853                })
854                .collect(),
855            Expected::Definition { logos, .. }
856            | Expected::Theorem { logos, .. }
857            | Expected::Axiom { logos, .. }
858            | Expected::Quotient { logos, .. } => {
859                vec![logos]
860            }
861        }
862    }
863}
864
865/// Is what `ctx` holds exactly what `expected` says?
866fn compare(ctx: &Context, expected: &Expected) -> std::result::Result<(), EmissionFault> {
867    let levels_match = |logos: &str, want: &[String], got: &[String]| {
868        if want == got {
869            Ok(())
870        } else {
871            Err(EmissionFault::Levels {
872                logos: logos.to_string(),
873                expected: want.to_vec(),
874                found: got.to_vec(),
875            })
876        }
877    };
878    match expected {
879        Expected::Definition {
880            logos,
881            levels,
882            ty,
883            body,
884        } => {
885            let Some((got_levels, got_ty, got_body)) = definition_payload(ctx, logos) else {
886                return Err(if header(ctx, logos).is_some() {
887                    EmissionFault::Kind {
888                        logos: logos.clone(),
889                        expected: DeclarationKind::Definition,
890                    }
891                } else {
892                    EmissionFault::Missing {
893                        logos: logos.clone(),
894                    }
895                });
896            };
897            if ctx.checked_opaque_dependencies(logos).is_some() {
898                return Err(EmissionFault::Kind {
899                    logos: logos.clone(),
900                    expected: DeclarationKind::Definition,
901                });
902            }
903            levels_match(logos, levels, &got_levels)?;
904            if !terms::alpha_eq(ty, &got_ty) {
905                return Err(EmissionFault::Type {
906                    logos: logos.clone(),
907                });
908            }
909            if !terms::alpha_eq(body, &got_body) {
910                return Err(EmissionFault::Body {
911                    logos: logos.clone(),
912                });
913            }
914            Ok(())
915        }
916        Expected::Quotient { logos, levels, ty } => {
917            let Some((got_levels, got_ty)) = header(ctx, logos) else {
918                return Err(EmissionFault::Missing {
919                    logos: logos.clone(),
920                });
921            };
922            let primitive =
923                ctx.quotient_primitives.get(logos).map(|c| c.kind) == quotient_kind(logos);
924            if !primitive || definition_payload(ctx, logos).is_some() {
925                return Err(EmissionFault::Kind {
926                    logos: logos.clone(),
927                    expected: DeclarationKind::Quotient,
928                });
929            }
930            levels_match(logos, levels, &got_levels)?;
931            if !terms::alpha_eq(ty, &got_ty) {
932                return Err(EmissionFault::Type {
933                    logos: logos.clone(),
934                });
935            }
936            Ok(())
937        }
938        Expected::Theorem {
939            logos, levels, ty, ..
940        }
941        | Expected::Axiom { logos, levels, ty } => {
942            // A theorem is checked AND unfolds; an `Opaque` is checked and sealed; an axiom is neither.
943            let (checked_expected, body, kind) = match expected {
944                Expected::Theorem {
945                    body: Some(body), ..
946                } => (true, Some(body), DeclarationKind::Theorem),
947                Expected::Theorem { body: None, .. } => (true, None, DeclarationKind::Opaque),
948                _ => (false, None, DeclarationKind::Axiom),
949            };
950            let Some((got_levels, got_ty)) = header(ctx, logos) else {
951                return Err(EmissionFault::Missing {
952                    logos: logos.clone(),
953                });
954            };
955            let payload = definition_payload(ctx, logos);
956            let checked = ctx.checked_opaque_dependencies(logos).is_some();
957            if payload.is_some() != body.is_some()
958                || ctx.is_inductive(logos)
959                || ctx.is_constructor(logos)
960                || checked != checked_expected
961            {
962                return Err(EmissionFault::Kind {
963                    logos: logos.clone(),
964                    expected: kind,
965                });
966            }
967            levels_match(logos, levels, &got_levels)?;
968            if !terms::alpha_eq(ty, &got_ty) {
969                return Err(EmissionFault::Type {
970                    logos: logos.clone(),
971                });
972            }
973            if let (Some(want), Some((_, _, got))) = (body, payload) {
974                if !terms::alpha_eq(want, &got) {
975                    return Err(EmissionFault::Body {
976                        logos: logos.clone(),
977                    });
978                }
979            }
980            Ok(())
981        }
982        Expected::Inductive { members, .. } => {
983            for member in members {
984                let logos = &member.logos;
985                if !ctx.is_inductive(logos) {
986                    return Err(if header(ctx, logos).is_some() {
987                        EmissionFault::Kind {
988                            logos: logos.clone(),
989                            expected: DeclarationKind::Inductive,
990                        }
991                    } else {
992                        EmissionFault::Missing {
993                            logos: logos.clone(),
994                        }
995                    });
996                }
997                let (got_levels, arity) =
998                    header(ctx, logos).ok_or_else(|| EmissionFault::Missing {
999                        logos: logos.clone(),
1000                    })?;
1001                let got_levels = if ctx.is_universe_poly_inductive(logos) {
1002                    got_levels
1003                } else {
1004                    Vec::new()
1005                };
1006                levels_match(logos, &member.levels, &got_levels)?;
1007                if !terms::alpha_eq(&member.arity, &arity) {
1008                    return Err(EmissionFault::Type {
1009                        logos: logos.clone(),
1010                    });
1011                }
1012                let ctors = ctx.get_constructors(logos);
1013                let same = ctx.inductive_num_params(logos) == member.params
1014                    && ctors.len() == member.constructors.len()
1015                    && ctors.iter().zip(&member.constructors).all(
1016                        |((name, ty), (want, want_ty))| {
1017                            name == want && terms::alpha_eq(want_ty, ty)
1018                        },
1019                    );
1020                if !same {
1021                    return Err(EmissionFault::Constructors {
1022                        logos: logos.clone(),
1023                    });
1024                }
1025                // η is registered exactly when the import registered it, with the same projections.
1026                match (&member.structure, ctx.struct_info(logos)) {
1027                    (None, None) => {}
1028                    (Some(want), Some(info)) => {
1029                        let names: Vec<&str> = want.iter().map(|(p, _, _)| p.as_str()).collect();
1030                        if info
1031                            .projections
1032                            .iter()
1033                            .map(String::as_str)
1034                            .ne(names.iter().copied())
1035                        {
1036                            return Err(EmissionFault::Structure {
1037                                logos: logos.clone(),
1038                            });
1039                        }
1040                        for (projection, ty, body) in want {
1041                            let Some((got_levels, got_ty, got_body)) =
1042                                definition_payload(ctx, projection)
1043                            else {
1044                                return Err(EmissionFault::Missing {
1045                                    logos: projection.clone(),
1046                                });
1047                            };
1048                            levels_match(projection, &member.levels, &got_levels)?;
1049                            if !terms::alpha_eq(ty, &got_ty) {
1050                                return Err(EmissionFault::Type {
1051                                    logos: projection.clone(),
1052                                });
1053                            }
1054                            if !terms::alpha_eq(body, &got_body) {
1055                                return Err(EmissionFault::Body {
1056                                    logos: projection.clone(),
1057                                });
1058                            }
1059                        }
1060                    }
1061                    _ => {
1062                        return Err(EmissionFault::Structure {
1063                            logos: logos.clone(),
1064                        })
1065                    }
1066                }
1067            }
1068            Ok(())
1069        }
1070    }
1071}
1072
1073/// Where a registration differs from its expectation, for a diagnostic a reader can act on.
1074fn mismatch_detail(ctx: &Context, expected: &Expected) -> String {
1075    let Expected::Inductive { members, .. } = expected else {
1076        return String::new();
1077    };
1078    let mut out = String::new();
1079    for m in members {
1080        let found_params = ctx.inductive_num_params(&m.logos);
1081        if found_params != m.params {
1082            out.push_str(&format!(
1083                " (`{}` has {found_params} parameters, not {})",
1084                m.logos, m.params
1085            ));
1086        }
1087        if let Some((_, arity)) = header(ctx, &m.logos) {
1088            if !terms::alpha_eq(&m.arity, &arity) {
1089                out.push_str(&format!(" (`{}`: arity {arity}, not {})", m.logos, m.arity));
1090            }
1091        }
1092        let found = ctx.get_constructors(&m.logos);
1093        if found.len() != m.constructors.len() {
1094            out.push_str(&format!(
1095                " (`{}` has {} constructors, not {})",
1096                m.logos,
1097                found.len(),
1098                m.constructors.len()
1099            ));
1100        }
1101        for ((name, ty), (want, want_ty)) in found.iter().zip(&m.constructors) {
1102            if name != want {
1103                out.push_str(&format!(
1104                    " (constructor `{name}` where `{want}` was written)"
1105                ));
1106            } else if !terms::alpha_eq(want_ty, ty) {
1107                out.push_str(&format!(" (`{name}`: {ty}, not {want_ty})"));
1108            }
1109        }
1110    }
1111    out
1112}
1113
1114/// Reparse and compare. Returns the checked context and every constant the fresh context DERIVED
1115/// while reading the document (an inductive's recursor family, a definition's equation lemmas), each
1116/// with the LOGOS declaration that produced it. With `listed`, the derived constants must be exactly
1117/// the correspondence's derived entries.
1118fn verify_on_this_stack(
1119    document: &EmittedDocument,
1120    listed_derivations: bool,
1121) -> std::result::Result<(Repl, BTreeMap<String, String>), EmissionFault> {
1122    let mut repl = Repl::new();
1123    let prelude = repl.context().clone();
1124    repl.execute_document(&document.source)
1125        .map_err(|e| EmissionFault::Reparse {
1126            message: e.to_string(),
1127        })?;
1128
1129    let expected_commands: usize = document.expected.iter().map(Expected::commands).sum();
1130    let found_commands = count_commands(&document.source)?;
1131    if found_commands != expected_commands {
1132        return Err(EmissionFault::CommandCount {
1133            expected: expected_commands,
1134            found: found_commands,
1135        });
1136    }
1137    for expected in &document.expected {
1138        compare(repl.context(), expected)?;
1139    }
1140    let ctx = repl.context();
1141
1142    // The second kernel: every transparent definition's body is inferred again by the independent
1143    // re-checker and must have exactly the declared type.
1144    for expected in &document.expected {
1145        if let Expected::Definition {
1146            logos, ty, body, ..
1147        } = expected
1148        {
1149            let rechecked = crate::recheck(ctx, body).map_err(|_| EmissionFault::Recheck {
1150                logos: logos.clone(),
1151            })?;
1152            if !crate::defeq_for_test(ctx, &rechecked, ty) {
1153                return Err(EmissionFault::Recheck {
1154                    logos: logos.clone(),
1155                });
1156            }
1157        }
1158    }
1159
1160    let listed: HashSet<&str> = document
1161        .expected
1162        .iter()
1163        .flat_map(Expected::logos_keys)
1164        .collect();
1165    let mut declared: BTreeSet<String> = BTreeSet::new();
1166    declared.extend(ctx.iter_declarations().map(|(k, _)| k.to_string()));
1167    declared.extend(ctx.iter_definitions().map(|(k, _, _)| k.to_string()));
1168    for (k, _) in ctx.iter_inductives() {
1169        declared.insert(k.to_string());
1170        declared.extend(
1171            ctx.get_constructors(k)
1172                .into_iter()
1173                .map(|(c, _)| c.to_string()),
1174        );
1175    }
1176    for key in listed.iter() {
1177        declared.remove(*key);
1178    }
1179    // What the fresh context derives on its own for an inductive the document declares — the
1180    // recursor family, no-confusion, injectivity, `below`/`brecOn`, decidable equality, and the
1181    // equation lemmas of a definition — is LOGOS's own checked derivation, not a declaration the
1182    // document smuggled in. Such a constant is TRANSPARENT (it has a body); a smuggled inductive,
1183    // constructor or axiom under a derived name has none, and the command count already refuses a
1184    // declaration written beside the listed ones.
1185    let family = derived_family(&document.expected);
1186    let mut derived: BTreeMap<String, String> = BTreeMap::new();
1187    for key in declared {
1188        let in_prelude = prelude.get_global(&key).is_some()
1189            || prelude.poly_declaration(&key).is_some()
1190            || prelude.is_inductive(&key);
1191        if in_prelude {
1192            continue;
1193        }
1194        let owner = family
1195            .get(&key)
1196            .cloned()
1197            .or_else(|| equation_head(&key, &listed, &family));
1198        match owner {
1199            Some(owner) if definition_payload(ctx, &key).is_some() => {
1200                derived.insert(key, owner);
1201            }
1202            _ => return Err(EmissionFault::Undeclared { logos: key }),
1203        }
1204    }
1205    if listed_derivations {
1206        let recorded: BTreeSet<&str> = document
1207            .correspondence
1208            .iter()
1209            .filter(|c| c.importer_key.is_empty())
1210            .map(|c| c.logos.as_str())
1211            .collect();
1212        if let Some(key) = derived.keys().find(|k| !recorded.contains(k.as_str())) {
1213            return Err(EmissionFault::Undeclared { logos: key.clone() });
1214        }
1215        if let Some(key) = recorded.iter().find(|k| !derived.contains_key(**k)) {
1216            return Err(EmissionFault::Missing {
1217                logos: key.to_string(),
1218            });
1219        }
1220    }
1221    Ok((repl, derived))
1222}
1223
1224/// The names the fresh context derives for the inductives a document writes — the families
1225/// `recursor::derive_auxiliaries` produces at declaration time — each with its inductive.
1226fn derived_family(expected: &[Expected]) -> HashMap<String, String> {
1227    let mut family = HashMap::new();
1228    for unit in expected {
1229        if let Expected::Inductive {
1230            members,
1231            written: true,
1232        } = unit
1233        {
1234            for member in members {
1235                for name in aux_family(
1236                    &member.logos,
1237                    member.constructors.iter().map(|(c, _)| c.as_str()),
1238                ) {
1239                    family.insert(name, member.logos.clone());
1240                }
1241            }
1242        }
1243    }
1244    family
1245}
1246
1247/// The declaration an equation lemma was generated for, when it is a listed definition or a derived
1248/// constant.
1249fn equation_head(
1250    key: &str,
1251    listed: &HashSet<&str>,
1252    family: &HashMap<String, String>,
1253) -> Option<String> {
1254    if !crate::equations::is_equation_name(key) {
1255        return None;
1256    }
1257    let head = match key.strip_suffix(crate::equations::UNFOLD_SUFFIX) {
1258        Some(head) => head,
1259        None => &key[..key.rfind(crate::equations::EQUATION_INFIX)?],
1260    };
1261    (listed.contains(head) || family.contains_key(head)).then(|| head.to_string())
1262}
1263
1264/// The constants `recursor::derive_auxiliaries` names for the inductive `inductive` with the
1265/// constructors `ctors`: the recursor family, no-confusion, `below`/`brecOn`, decidable equality, and
1266/// one injectivity lemma per constructor.
1267fn aux_family<'n>(inductive: &str, ctors: impl Iterator<Item = &'n str>) -> Vec<String> {
1268    let mut names: Vec<String> = [
1269        "_rec",
1270        "_recOn",
1271        "_casesOn",
1272        "_noConfusionType",
1273        "_noConfusion",
1274        "_below",
1275        "_brecOn",
1276    ]
1277    .iter()
1278    .map(|suffix| format!("{inductive}{suffix}"))
1279    .collect();
1280    names.push(format!("decEq{inductive}"));
1281    names.extend(ctors.map(|c| format!("{c}_inj")));
1282    names
1283}
1284
1285/// An equation lemma the fresh context generated for a listed definition.
1286fn is_equation_of_listed(key: &str, listed: &HashSet<&str>) -> bool {
1287    if !crate::equations::is_equation_name(key) {
1288        return false;
1289    }
1290    let head = match key.strip_suffix(crate::equations::UNFOLD_SUFFIX) {
1291        Some(head) => head,
1292        None => &key[..key.rfind(crate::equations::EQUATION_INFIX).unwrap_or(0)],
1293    };
1294    listed.contains(head)
1295}
1296
1297/// The number of declaration commands in `source`, read through the text-only door — the prose
1298/// blocks excluded.
1299fn count_commands(source: &str) -> std::result::Result<usize, EmissionFault> {
1300    let surface = crate::interface::Surface::new();
1301    let mut offset = 0;
1302    let mut count = 0;
1303    while !source[offset..].trim().is_empty() {
1304        let (command, consumed) =
1305            crate::interface::parse_command_prefix_in(&surface, &source[offset..]).map_err(
1306                |e| EmissionFault::Reparse {
1307                    message: e.to_string(),
1308                },
1309            )?;
1310        if consumed == 0 {
1311            return Err(EmissionFault::Reparse {
1312                message: "the document parser made no progress".into(),
1313            });
1314        }
1315        if !matches!(command, Command::Comment) {
1316            count += 1;
1317        }
1318        offset += consumed;
1319    }
1320    Ok(count)
1321}
1322
1323// ─────────────────────────────────────────────────────────────────────────────────────────────
1324// The emitter
1325// ─────────────────────────────────────────────────────────────────────────────────────────────
1326
1327fn unsupported(message: impl Into<String>) -> Verdict {
1328    declined(DiagnosticCode::UnsupportedFeature, message)
1329}
1330
1331/// Section order: assumptions, types, definitions, theorems; the target last of all.
1332fn rank(decl: &Decl) -> u8 {
1333    match decl {
1334        Decl::Axiom { .. } => 0,
1335        Decl::Quotient { .. } => 1,
1336        Decl::Prelude { .. } | Decl::Inductive { .. } => 1,
1337        Decl::Definition { .. } => 2,
1338        Decl::Theorem { .. } => 3,
1339    }
1340}
1341
1342const SECTIONS: [&str; 5] = [
1343    "Assumptions",
1344    "Types",
1345    "Definitions",
1346    "Theorems",
1347    "The target",
1348];
1349
1350struct Emitter<'a> {
1351    ctx: &'a Context,
1352    closure: &'a Closure,
1353    /// Importer key → absolute LOGOS key.
1354    logos: BTreeMap<String, String>,
1355    /// Per unit, the universe parameter renaming.
1356    levels: Vec<Vec<(String, String)>>,
1357    /// Per unit, the helper's owner (by Lean name) for a reader-introduced helper.
1358    owners: BTreeMap<usize, String>,
1359    relocated_under: Option<String>,
1360    /// Every component of every name the document writes — no binder may take one.
1361    avoid: HashSet<String>,
1362    /// Every name the LOGOS prelude declares — no binder may take one either, or it would read as
1363    /// that constant.
1364    prelude_names: HashSet<String>,
1365    prelude: Context,
1366    /// Every Lean binder name the document could not keep: (declaration, Lean name, written, reason),
1367    /// in the order the document writes them.
1368    renames: std::cell::RefCell<Vec<(String, terms::Rename)>>,
1369    /// Whether a hygienic (inaccessible) Lean binder was written under its user-facing name.
1370    hygienic: std::cell::Cell<bool>,
1371}
1372
1373impl<'a> Emitter<'a> {
1374    fn new(ctx: &'a Context, closure: &'a Closure) -> Result<Emitter<'a>> {
1375        let prelude = crate::interface::standard_context();
1376        let mut taken = names::Taken {
1377            roots: HashSet::new(),
1378            keys: HashSet::new(),
1379        };
1380        {
1381            let mut add = |k: &str| {
1382                taken
1383                    .roots
1384                    .insert(k.split('.').next().unwrap_or(k).to_string());
1385                taken.keys.insert(k.to_string());
1386            };
1387            prelude.iter_declarations().for_each(|(k, _)| add(k));
1388            prelude.iter_definitions().for_each(|(k, _, _)| add(k));
1389            for (k, _) in prelude.iter_inductives() {
1390                add(k);
1391                for (c, _) in prelude.get_constructors(k) {
1392                    add(c);
1393                }
1394            }
1395        }
1396
1397        // A structure's projections are the reader's, named `<Inductive>.proj_<i>` once the inductive
1398        // has its LOGOS name.
1399        let mut projection_owner: BTreeMap<String, (String, usize)> = BTreeMap::new();
1400        for decl in &closure.decls {
1401            if let Decl::Inductive { members } = decl {
1402                for member in members {
1403                    for (i, projection) in projections_of(ctx, member).into_iter().enumerate() {
1404                        projection_owner.insert(projection, (member.clone(), i + 1));
1405                    }
1406                }
1407            }
1408        }
1409        let mut lean_named: BTreeSet<String> = BTreeSet::new();
1410        let mut helpers: Vec<String> = Vec::new();
1411        let mut fixed: BTreeMap<String, String> = BTreeMap::new();
1412        for key in closure.unit_of.keys() {
1413            if PRELUDE_IDENTITIES.contains(&key.as_str()) || quotient_kind(key).is_some() {
1414                fixed.insert(key.clone(), key.clone());
1415            } else if key.starts_with(HELPER_PREFIX) {
1416                helpers.push(key.clone());
1417            } else if projection_owner.contains_key(key) {
1418                continue;
1419            } else if names::lean_name(key).is_some() {
1420                lean_named.insert(key.clone());
1421            } else {
1422                return Err(unsupported(format!(
1423                    "the kernel constant `{key}` has neither a Lean name nor a known reader origin"
1424                )));
1425            }
1426        }
1427
1428        let definitions: BTreeSet<&str> = closure
1429            .decls
1430            .iter()
1431            .filter_map(|d| match d {
1432                Decl::Definition { key, .. } => Some(key.as_str()),
1433                _ => None,
1434            })
1435            .collect();
1436        let inductive_members: Vec<(&str, Vec<String>)> = closure
1437            .decls
1438            .iter()
1439            .filter_map(|d| match d {
1440                Decl::Inductive { members } => Some(members),
1441                _ => None,
1442            })
1443            .flatten()
1444            .map(|m| {
1445                (
1446                    m.as_str(),
1447                    ctx.get_constructors(m)
1448                        .into_iter()
1449                        .map(|(c, _)| c.to_string())
1450                        .collect(),
1451                )
1452            })
1453            .collect();
1454        let reserved = |allocation: &BTreeMap<String, String>| -> BTreeSet<String> {
1455            let mut derived = BTreeSet::new();
1456            for (member, ctors) in &inductive_members {
1457                let Some(l) = allocation.get(*member) else {
1458                    continue;
1459                };
1460                derived.extend(aux_family(
1461                    l,
1462                    ctors
1463                        .iter()
1464                        .filter_map(|c| allocation.get(c))
1465                        .map(String::as_str),
1466                ));
1467            }
1468            let written_definitions: HashSet<&str> = definitions
1469                .iter()
1470                .filter_map(|k| allocation.get(*k))
1471                .map(String::as_str)
1472                .collect();
1473            for logos in allocation.values() {
1474                if is_equation_of_listed(logos, &written_definitions) {
1475                    derived.insert(logos.clone());
1476                }
1477            }
1478            derived
1479        };
1480        let allocation = names::allocate(&lean_named, &fixed, &taken, &reserved)
1481            .map_err(|e| unsupported(format!("LOGOS name allocation: {e}")))?;
1482        let mut logos = allocation.logos;
1483
1484        for (projection, (owner, i)) in &projection_owner {
1485            let mut name = format!("{}.proj_{i}", logos[owner]);
1486            while logos.values().any(|v| *v == name) || taken.keys.contains(&name) {
1487                name.push('\'');
1488            }
1489            logos.insert(projection.clone(), name);
1490        }
1491
1492        // A helper is named inside the declaration whose sharing it preserves.
1493        let mut owners: BTreeMap<usize, String> = BTreeMap::new();
1494        let mut referrers: Vec<BTreeSet<usize>> = vec![BTreeSet::new(); closure.decls.len()];
1495        for (from, deps) in closure.deps.iter().enumerate() {
1496            for &to in deps {
1497                referrers[to].insert(from);
1498            }
1499        }
1500        helpers.sort_by_key(|h| h[HELPER_PREFIX.len()..].parse::<u64>().unwrap_or(u64::MAX));
1501        let mut per_owner: BTreeMap<String, usize> = BTreeMap::new();
1502        for helper in &helpers {
1503            let unit = closure.unit_of[helper];
1504            let owner_unit = owner_of(unit, &referrers, closure).ok_or_else(|| {
1505                unsupported(format!(
1506                    "the reader's helper `{helper}` is not used by any declaration of the closure"
1507                ))
1508            })?;
1509            let owner_key = closure.decls[owner_unit].key().to_string();
1510            let owner_logos = logos[&owner_key].clone();
1511            let count = per_owner.entry(owner_logos.clone()).or_insert(0);
1512            *count += 1;
1513            let mut name = format!("{owner_logos}.shared_{count}");
1514            while logos.values().any(|v| *v == name) || taken.keys.contains(&name) {
1515                name.push('\'');
1516            }
1517            logos.insert(helper.clone(), name);
1518            owners.insert(unit, display_key(&owner_key));
1519        }
1520
1521        for (key, name) in &logos {
1522            if fixed.contains_key(key) {
1523                continue;
1524            }
1525            let clashes = prelude.get_global(name).is_some()
1526                || prelude.poly_declaration(name).is_some()
1527                || prelude.get_universe_poly(name).is_some();
1528            let spellable =
1529                crate::interface::declaration_name(name).is_ok_and(|n| n.key() == *name);
1530            if clashes || !spellable {
1531                return Err(unsupported(format!(
1532                    "the LOGOS name `{name}` allocated for `{}` is not a free, spellable name",
1533                    display_key(key)
1534                )));
1535            }
1536        }
1537
1538        let mut avoid: HashSet<String> = HashSet::new();
1539        for name in logos.values() {
1540            avoid.extend(name.split('.').map(str::to_string));
1541        }
1542        avoid.extend(allocation.relocated_under.iter().cloned());
1543
1544        let levels = closure
1545            .decls
1546            .iter()
1547            .map(|d| match d {
1548                Decl::Definition { levels, .. }
1549                | Decl::Theorem { levels, .. }
1550                | Decl::Axiom { levels, .. }
1551                | Decl::Quotient { levels, .. } => level_names(levels),
1552                Decl::Inductive { members } | Decl::Prelude { members } => level_names(
1553                    ctx.poly_declaration(&members[0])
1554                        .filter(|_| ctx.is_universe_poly_inductive(&members[0]))
1555                        .map(|(l, _)| l)
1556                        .unwrap_or(&[]),
1557                ),
1558            })
1559            .collect();
1560
1561        let prelude_names: HashSet<String> =
1562            taken.roots.iter().chain(&taken.keys).cloned().collect();
1563
1564        Ok(Emitter {
1565            ctx,
1566            closure,
1567            logos,
1568            levels,
1569            owners,
1570            relocated_under: allocation.relocated_under,
1571            avoid,
1572            prelude_names,
1573            prelude,
1574            renames: std::cell::RefCell::new(Vec::new()),
1575            hygienic: std::cell::Cell::new(false),
1576        })
1577    }
1578
1579    fn rename(&self, unit: usize, term: &Term) -> Result<Term> {
1580        let levels: HashMap<String, String> = self.levels[unit].iter().cloned().collect();
1581        let globals = |k: &str| self.logos.get(k).cloned();
1582        terms::rename(term, &globals, &levels).map_err(|fault| {
1583            unsupported(format!(
1584                "`{}`: {fault}",
1585                display_key(self.closure.decls[unit].key())
1586            ))
1587        })
1588    }
1589
1590    fn level_list(&self, unit: usize) -> Vec<String> {
1591        self.levels[unit].iter().map(|(_, l)| l.clone()).collect()
1592    }
1593
1594    /// What unit `unit` must reparse to.
1595    fn expected(&self, unit: usize) -> Result<Expected> {
1596        Ok(match &self.closure.decls[unit] {
1597            Decl::Definition { key, ty, body, .. } => Expected::Definition {
1598                logos: self.logos[key].clone(),
1599                levels: self.level_list(unit),
1600                ty: self.rename(unit, ty)?,
1601                body: self.rename(unit, body)?,
1602            },
1603            Decl::Theorem {
1604                key,
1605                ty,
1606                proof,
1607                transparent,
1608                ..
1609            } => Expected::Theorem {
1610                logos: self.logos[key].clone(),
1611                levels: self.level_list(unit),
1612                ty: self.rename(unit, ty)?,
1613                body: if *transparent {
1614                    Some(self.rename(unit, proof)?)
1615                } else {
1616                    None
1617                },
1618            },
1619            Decl::Quotient { key, ty, .. } => Expected::Quotient {
1620                logos: self.logos[key].clone(),
1621                levels: self.level_list(unit),
1622                ty: self.rename(unit, ty)?,
1623            },
1624            Decl::Axiom { key, ty, .. } => Expected::Axiom {
1625                logos: self.logos[key].clone(),
1626                levels: self.level_list(unit),
1627                ty: self.rename(unit, ty)?,
1628            },
1629            Decl::Inductive { members } | Decl::Prelude { members } => {
1630                let mut out = Vec::with_capacity(members.len());
1631                for member in members {
1632                    let arity = self.ctx.get_global(member).ok_or_else(|| {
1633                        unsupported(format!(
1634                            "the inductive `{}` has no arity",
1635                            display_key(member)
1636                        ))
1637                    })?;
1638                    out.push(ExpectedInductive {
1639                        logos: self.logos[member].clone(),
1640                        levels: self.level_list(unit),
1641                        arity: self.rename(unit, arity)?,
1642                        params: self.ctx.inductive_num_params(member),
1643                        constructors: self
1644                            .ctx
1645                            .get_constructors(member)
1646                            .into_iter()
1647                            .map(|(c, ty)| Ok((self.logos[c].clone(), self.rename(unit, ty)?)))
1648                            .collect::<Result<_>>()?,
1649                        structure: match self.ctx.struct_info(member) {
1650                            None => None,
1651                            Some(info) => Some(
1652                                info.projections
1653                                    .iter()
1654                                    .map(|p| {
1655                                        let (_, ty, body) = definition_payload(self.ctx, p).ok_or_else(|| {
1656                                            unsupported(format!("the projection `{p}` has no checked definition"))
1657                                        })?;
1658                                        Ok((self.logos[p].clone(), self.rename(unit, &ty)?, self.rename(unit, &body)?))
1659                                    })
1660                                    .collect::<Result<_>>()?,
1661                            ),
1662                        },
1663                    });
1664                }
1665                Expected::Inductive {
1666                    members: out,
1667                    written: matches!(self.closure.decls[unit], Decl::Inductive { .. }),
1668                }
1669            }
1670        })
1671    }
1672
1673    /// The order the document declares its units in: every unit after the units it references,
1674    /// grouped by section as far as that allows, the target last.
1675    fn order(&self) -> Vec<usize> {
1676        let n = self.closure.decls.len();
1677        let mut waiting: Vec<usize> = self.closure.deps.iter().map(BTreeSet::len).collect();
1678        let mut referrers: Vec<Vec<usize>> = vec![Vec::new(); n];
1679        for (from, deps) in self.closure.deps.iter().enumerate() {
1680            for &to in deps {
1681                referrers[to].push(from);
1682            }
1683        }
1684        let priority = |u: usize| {
1685            let r = if u == self.closure.target {
1686                4
1687            } else {
1688                rank(&self.closure.decls[u])
1689            };
1690            (r, self.logos[self.closure.decls[u].key()].clone(), u)
1691        };
1692        let mut ready: BTreeSet<(u8, String, usize)> =
1693            (0..n).filter(|&u| waiting[u] == 0).map(priority).collect();
1694        let mut order = Vec::with_capacity(n);
1695        while let Some(next) = ready.pop_first() {
1696            let u = next.2;
1697            order.push(u);
1698            for &r in &referrers[u] {
1699                waiting[r] -= 1;
1700                if waiting[r] == 0 {
1701                    ready.insert(priority(r));
1702                }
1703            }
1704        }
1705        order
1706    }
1707
1708    fn run(self) -> Result<EmittedDocument> {
1709        let order = self.order();
1710        if order.len() != self.closure.decls.len() {
1711            return Err(unsupported(
1712                "the imported declarations reference each other in a cycle",
1713            ));
1714        }
1715
1716        let mut expected = Vec::with_capacity(order.len());
1717        let mut blocks: Vec<(usize, String, Form)> = Vec::new();
1718        let mut omissions = Vec::new();
1719        let mut probe = Repl::new();
1720
1721        for &unit in &order {
1722            let want = self.expected(unit)?;
1723            if let Decl::Prelude { .. } = self.closure.decls[unit] {
1724                compare(&self.prelude, &want).map_err(|fault| {
1725                    unsupported(format!(
1726                        "the imported `Nat` is not the LOGOS prelude's `Nat`: {fault}"
1727                    ))
1728                })?;
1729                expected.push(want);
1730                continue;
1731            }
1732            let written = self.written(unit, &want, probe.context())?;
1733            let (text, form) = match self.literate(&written) {
1734                Ok(candidate) => {
1735                    let mut scratch = Repl::new();
1736                    *scratch.context_mut() = probe.context().clone();
1737                    let exact = scratch.execute_document(&candidate).is_ok()
1738                        && compare(scratch.context(), &want).is_ok();
1739                    if exact {
1740                        (candidate, Form::Literate)
1741                    } else {
1742                        omissions.push(format!(
1743                            "{}: the literate form did not reparse to the identical declaration",
1744                            written.logos_name()
1745                        ));
1746                        (self.vernacular(&written)?, Form::Vernacular)
1747                    }
1748                }
1749                Err(why) => {
1750                    omissions.push(format!("{}: {why}", written.logos_name()));
1751                    (self.vernacular(&written)?, Form::Vernacular)
1752                }
1753            };
1754            probe.execute_document(&text).map_err(|e| {
1755                unsupported(format!(
1756                    "LOGOS refused the emitted declaration of `{}`: {e}",
1757                    display_key(self.closure.decls[unit].key())
1758                ))
1759            })?;
1760            compare(probe.context(), &want).map_err(|fault| {
1761                unsupported(format!(
1762                    "the emitted declaration of `{}` did not reparse to the imported one: {fault}{}\n{text}",
1763                    display_key(self.closure.decls[unit].key()),
1764                    mismatch_detail(probe.context(), &want),
1765                ))
1766            })?;
1767            expected.push(want);
1768            blocks.push((unit, text, form));
1769        }
1770
1771        let (source, lines) = self.assemble(&blocks)?;
1772        if source.len() > MAX_SOURCE_BYTES {
1773            return Err(declined(
1774                DiagnosticCode::ResourceLimit,
1775                format!(
1776                    "the emitted document is {} bytes, over the {MAX_SOURCE_BYTES}-byte limit",
1777                    source.len()
1778                ),
1779            ));
1780        }
1781        let forms: BTreeMap<usize, Form> = blocks.iter().map(|(u, _, f)| (*u, *f)).collect();
1782        let correspondence = self.correspondence(&order, &forms, &lines);
1783        let assumptions = order
1784            .iter()
1785            .filter_map(|&u| match &self.closure.decls[u] {
1786                Decl::Axiom { key, .. } => Some(self.logos[key].clone()),
1787                _ => None,
1788            })
1789            .collect();
1790        let document = EmittedDocument {
1791            source,
1792            target: self.logos[&self.closure.target_key].clone(),
1793            correspondence,
1794            omissions,
1795            assumptions,
1796            losses: self.losses(),
1797            expected,
1798        };
1799        let (_, derived) = verify_on_this_stack(&document, false).map_err(|fault| {
1800            unsupported(format!(
1801                "the emitted document did not survive its fresh reparse: {fault}"
1802            ))
1803        })?;
1804        // Every constant the fresh context derived while reading the document is listed too, with the
1805        // written declaration that produced it: nothing the document introduces goes unaccounted.
1806        let mut document = document;
1807        for (logos, owner) in derived {
1808            let producer = document
1809                .correspondence
1810                .iter()
1811                .find(|c| c.logos == owner)
1812                .cloned();
1813            document.correspondence.push(Correspondence {
1814                lean: String::new(),
1815                importer_key: String::new(),
1816                logos,
1817                kind: DeclarationKind::Definition,
1818                origin: Origin::Synthesized,
1819                form: producer.as_ref().map_or(Form::Vernacular, |p| p.form),
1820                levels: Vec::new(),
1821                lines: producer.and_then(|p| p.lines),
1822            });
1823        }
1824        Ok(document)
1825    }
1826
1827    /// A unit as the document spells it: binders fresh, references written relative to the
1828    /// namespace the declaration elaborates in.
1829    fn written(&self, unit: usize, want: &Expected, probe: &Context) -> Result<Written> {
1830        // Exactly what the reading `Repl` knows when it resolves a name in this declaration: the keys the
1831        // command itself declares, and every constant already in its context, universe-polymorphic ones
1832        // (a table `get_global` does not read) included.
1833        let pending: Vec<String> = want.logos_keys().iter().map(|s| s.to_string()).collect();
1834        let known = |k: &str| {
1835            pending.iter().any(|p| p == k)
1836                || probe.get_global(k).is_some()
1837                || probe.poly_declaration(k).is_some()
1838        };
1839        let declared = self.logos[self.closure.decls[unit].key()].clone();
1840        let record = |renames: Vec<terms::Rename>| {
1841            self.renames
1842                .borrow_mut()
1843                .extend(renames.into_iter().map(|r| (declared.clone(), r)));
1844        };
1845        let unavailable = terms::Unavailable {
1846            constants: &self.avoid,
1847            prelude: &self.prelude_names,
1848        };
1849        // `named`: how many leading Π binders are written with a name even when nothing refers to them.
1850        let fresh_naming = |t: &Term, named: usize| {
1851            let mut renames = Vec::new();
1852            let mut hygienic = false;
1853            let out = terms::freshen_naming(t, named, &unavailable, &mut renames, &mut hygienic)
1854                .map_err(|fault| {
1855                    unsupported(format!(
1856                        "`{}`: {fault}",
1857                        display_key(self.closure.decls[unit].key())
1858                    ))
1859                })?;
1860            record(renames);
1861            self.hygienic.set(self.hygienic.get() || hygienic);
1862            Ok::<Term, Verdict>(out)
1863        };
1864        let fresh = |t: &Term| fresh_naming(t, 0);
1865        let spell_in = |declared: &str, t: &Term| -> Result<Term> {
1866            let scope = declaration_scope(declared);
1867            let mut failure = None;
1868            let out = terms::respell(t, &mut |key| match written_reference(&scope, &known, key) {
1869                Some(w) => w,
1870                None => {
1871                    failure = Some(key.to_string());
1872                    key.to_string()
1873                }
1874            });
1875            match failure {
1876                Some(key) => Err(unsupported(format!(
1877                    "no written name reaches `{key}` from `{declared}`"
1878                ))),
1879                None => Ok(out),
1880            }
1881        };
1882        Ok(match want {
1883            Expected::Definition {
1884                logos,
1885                levels,
1886                ty,
1887                body,
1888            } => Written::Definition {
1889                name: logos.clone(),
1890                levels: levels.clone(),
1891                ty: spell_in(logos, &fresh(ty)?)?,
1892                body: spell_in(logos, &fresh(body)?)?,
1893            },
1894            Expected::Quotient { logos, levels, .. } => {
1895                let Decl::Quotient { kind, equality, .. } = &self.closure.decls[unit] else {
1896                    unreachable!("a quotient's expectation comes from a quotient")
1897                };
1898                let equality = match equality {
1899                    Some(eq) => match spell_in(logos, &Term::Global(self.logos[eq].clone()))? {
1900                        Term::Global(written) => Some(written),
1901                        _ => unreachable!("a global respells to a global"),
1902                    },
1903                    None => None,
1904                };
1905                Written::Quotient {
1906                    name: logos.clone(),
1907                    kind: *kind,
1908                    levels: levels.clone(),
1909                    equality,
1910                }
1911            }
1912            Expected::Axiom { logos, levels, ty } => Written::Axiom {
1913                name: logos.clone(),
1914                levels: levels.clone(),
1915                ty: spell_in(logos, &fresh(ty)?)?,
1916            },
1917            Expected::Theorem {
1918                logos,
1919                levels,
1920                ty,
1921                body,
1922            } => {
1923                let Decl::Theorem { proof, .. } = &self.closure.decls[unit] else {
1924                    unreachable!("a theorem's expectation comes from a theorem")
1925                };
1926                Written::Theorem {
1927                    name: logos.clone(),
1928                    transparent: body.is_some(),
1929                    levels: levels.clone(),
1930                    ty: spell_in(logos, &fresh(ty)?)?,
1931                    proof: spell_in(logos, &fresh(&self.rename(unit, proof)?)?)?,
1932                }
1933            }
1934            Expected::Inductive { members, .. } => {
1935                let mut out = Vec::with_capacity(members.len());
1936                for m in members {
1937                    // The parameters are named: every constructor refers to them by name.
1938                    let arity = fresh_naming(&m.arity, m.params)?;
1939                    let (params, sort) = terms::split_pis(&arity, m.params).ok_or_else(|| {
1940                        unsupported(format!(
1941                            "the arity of `{}` has fewer binders than parameters",
1942                            m.logos
1943                        ))
1944                    })?;
1945                    let param_names: Vec<String> = params.iter().map(|(n, _)| n.clone()).collect();
1946                    let enclosing: Vec<&str> = {
1947                        let parts: Vec<&str> = m.logos.split('.').collect();
1948                        parts[..parts.len() - 1].to_vec()
1949                    };
1950                    // A constructor whose own parameter binders do not state the member's parameter types
1951                    // exactly (Lean's `HAdd.mk` keeps `outParam (Type w)` where the arity says `Type w`)
1952                    // is written with its FULL telescope; then every constructor of the member is.
1953                    let mut split = Vec::with_capacity(m.constructors.len());
1954                    for (ctor, ty) in &m.constructors {
1955                        let mut renames = Vec::new();
1956                        let mut hygienic = false;
1957                        let ty = terms::freshen_with_prefix(
1958                            ty,
1959                            &param_names,
1960                            &unavailable,
1961                            &mut renames,
1962                            &mut hygienic,
1963                        )
1964                        .map_err(|fault| unsupported(format!("`{ctor}`: {fault}")))?;
1965                        record(renames);
1966                        self.hygienic.set(self.hygienic.get() || hygienic);
1967                        let (restated, rest) =
1968                            terms::split_pis(&ty, m.params).ok_or_else(|| {
1969                                unsupported(format!(
1970                                    "the constructor `{ctor}` binds fewer parameters than its type"
1971                                ))
1972                            })?;
1973                        split.push((ctor, ty, restated, rest));
1974                    }
1975                    let full = split.iter().any(|(_, _, restated, _)| {
1976                        restated
1977                            .iter()
1978                            .zip(&params)
1979                            .any(|((_, t), (_, p))| !terms::alpha_eq(t, p))
1980                    });
1981                    let mut constructors = Vec::with_capacity(split.len());
1982                    for (ctor, ty, _, rest) in split {
1983                        let written = if full { ty } else { rest };
1984                        constructors
1985                            .push((relative_to(ctor, &enclosing), spell_in(&m.logos, &written)?));
1986                    }
1987                    let params = params
1988                        .into_iter()
1989                        .map(|(n, t)| Ok((n, spell_in(&m.logos, &t)?)))
1990                        .collect::<Result<Vec<_>>>()?;
1991                    let own = m
1992                        .logos
1993                        .split('.')
1994                        .next_back()
1995                        .unwrap_or(&m.logos)
1996                        .to_string();
1997                    let written_self =
1998                        written_reference(&declaration_scope(&m.logos), &known, &m.logos)
1999                            .unwrap_or_else(|| m.logos.clone());
2000                    let structure = match &m.structure {
2001                        None => None,
2002                        Some(projections) => Some(
2003                            projections
2004                                .iter()
2005                                .map(|(p, ty, body)| {
2006                                    Ok((
2007                                        p.clone(),
2008                                        spell_in(p, &fresh(ty)?)?,
2009                                        spell_in(p, &fresh(body)?)?,
2010                                    ))
2011                                })
2012                                .collect::<Result<Vec<_>>>()?,
2013                        ),
2014                    };
2015                    out.push(WrittenInductive {
2016                        name: m.logos.clone(),
2017                        levels: m.levels.clone(),
2018                        params,
2019                        sort: spell_in(&m.logos, &sort)?,
2020                        constructors,
2021                        full,
2022                        structure,
2023                        result_heads: vec![m.logos.clone(), written_self, own],
2024                    });
2025                }
2026                Written::Inductive(out)
2027            }
2028        })
2029    }
2030
2031    /// The vernacular command(s) for a written unit.
2032    fn vernacular(&self, written: &Written) -> Result<String> {
2033        let (levels, command) = match written {
2034            Written::Definition {
2035                name,
2036                levels,
2037                ty,
2038                body,
2039            } => (
2040                levels,
2041                Command::Definition {
2042                    name: name.clone(),
2043                    ty: Some(ty.clone()),
2044                    body: body.clone(),
2045                    is_hint: false,
2046                    implicit_count: 0,
2047                    proof_goal: None,
2048                },
2049            ),
2050            Written::Theorem {
2051                name,
2052                transparent: true,
2053                levels,
2054                ty,
2055                proof,
2056            } => (
2057                levels,
2058                Command::Theorem {
2059                    name: name.clone(),
2060                    ty: ty.clone(),
2061                    proof: proof.clone(),
2062                },
2063            ),
2064            Written::Theorem {
2065                name,
2066                transparent: false,
2067                levels,
2068                ty,
2069                proof,
2070            } => (
2071                levels,
2072                Command::Opaque {
2073                    name: name.clone(),
2074                    ty: ty.clone(),
2075                    value: proof.clone(),
2076                },
2077            ),
2078            Written::Axiom { name, levels, ty } => (
2079                levels,
2080                Command::Axiom {
2081                    name: name.clone(),
2082                    ty: ty.clone(),
2083                },
2084            ),
2085            Written::Quotient {
2086                kind,
2087                levels,
2088                equality,
2089                ..
2090            } => (
2091                levels,
2092                Command::Quotient {
2093                    kind: *kind,
2094                    equality: equality.clone(),
2095                },
2096            ),
2097            // A parameter is referred to by its NAME, exactly as the parser hands a written one
2098            // over: the block door binds it again when it puts the parameter prefix back.
2099            Written::Inductive(members) => (
2100                &members[0].levels,
2101                Command::InductiveBlock {
2102                    members: members
2103                        .iter()
2104                        .map(|m| {
2105                            let names: Vec<String> =
2106                                m.params.iter().map(|(n, _)| n.clone()).collect();
2107                            crate::interface::command::InductiveMember {
2108                                name: m.name.clone(),
2109                                params: m
2110                                    .params
2111                                    .iter()
2112                                    .enumerate()
2113                                    .map(|(i, (n, t))| {
2114                                        (n.clone(), terms::name_parameters(t, &names[..i]))
2115                                    })
2116                                    .collect(),
2117                                sort: terms::name_parameters(&m.sort, &names),
2118                                constructors: m
2119                                    .constructors
2120                                    .iter()
2121                                    .map(|(c, t)| {
2122                                        let t = if m.full {
2123                                            t.clone()
2124                                        } else {
2125                                            terms::name_parameters(t, &names)
2126                                        };
2127                                        (c.clone(), t)
2128                                    })
2129                                    .collect(),
2130                                full_constructors: m.full,
2131                            }
2132                        })
2133                        .collect(),
2134                },
2135            ),
2136        };
2137        let wrap = |command: Command| {
2138            if levels.is_empty() {
2139                command
2140            } else {
2141                Command::Universes {
2142                    params: levels.clone(),
2143                    inner: Box::new(command),
2144                }
2145            }
2146        };
2147        let mut commands = vec![wrap(command)];
2148        // A structure's projections and its η follow its block at once, before anything that relies
2149        // on η (its recursor's body checks only modulo η).
2150        if let Written::Inductive(members) = written {
2151            for m in members {
2152                let Some(projections) = &m.structure else {
2153                    continue;
2154                };
2155                for (p, ty, body) in projections {
2156                    commands.push(wrap(Command::Definition {
2157                        name: p.clone(),
2158                        ty: Some(ty.clone()),
2159                        body: body.clone(),
2160                        is_hint: false,
2161                        implicit_count: 0,
2162                        proof_goal: None,
2163                    }));
2164                }
2165                commands.push(Command::Structure {
2166                    inductive: m.name.clone(),
2167                    projections: projections.iter().map(|(p, _, _)| p.clone()).collect(),
2168                });
2169            }
2170        }
2171        let printed: Vec<String> = commands
2172            .iter()
2173            .map(print_command)
2174            .collect::<std::result::Result<_, _>>()
2175            .map_err(|e| unsupported(format!("`{}`: {e}", written.logos_name())))?;
2176        Ok(printed.join("\n\n"))
2177    }
2178
2179    /// The literate form of a written unit, or why there is none.
2180    fn literate(&self, written: &Written) -> std::result::Result<String, String> {
2181        match written {
2182            Written::Inductive(members) if members.iter().any(|m| m.structure.is_some()) => Err(
2183                "a structure's projections and η are declared by the vernacular `Structure` command, which \
2184                 the literate surface has no form for"
2185                    .into(),
2186            ),
2187            Written::Inductive(members) if members.iter().any(|m| m.full) => Err(
2188                "a constructor restates its parameters with types the inductive's arity does not use, \
2189                 which only the vernacular's full constructor telescope can write"
2190                    .into(),
2191            ),
2192            Written::Inductive(members) if members.len() == 1 && members[0].levels.is_empty() => {
2193                let m = &members[0];
2194                literate::inductive(&literate::WrittenInductive {
2195                    name: &m.name,
2196                    params: &m.params,
2197                    sort: &m.sort,
2198                    constructors: &m.constructors,
2199                    result_heads: &m.result_heads,
2200                })
2201            }
2202            Written::Inductive(members) if members.len() > 1 => {
2203                Err("a mutual block has no literate form".into())
2204            }
2205            Written::Inductive(_) => Err("the literate surface has no universe parameters".into()),
2206            Written::Definition { name, levels, ty, body } if levels.is_empty() => {
2207                literate::definition(name, ty, body)
2208            }
2209            Written::Definition { .. } => Err("the literate surface has no universe parameters".into()),
2210            Written::Theorem { .. } => Err(
2211                "the literate `## Theorem:` surface proves by tactic, and this proof is a checked term".into(),
2212            ),
2213            Written::Axiom { .. } => Err("the literate surface has no form for an assumption".into()),
2214            Written::Quotient { .. } => Err("the literate surface has no form for a quotient primitive".into()),
2215        }
2216    }
2217
2218    fn describe(&self, unit: usize) -> String {
2219        let decl = &self.closure.decls[unit];
2220        let lean = display_key(decl.key());
2221        match decl {
2222            Decl::Axiom { .. } => format!("Axiom `{lean}`: assumed, not proved."),
2223            Decl::Quotient { .. } => format!(
2224                "Quotient primitive `{lean}`: the kernel's certified quotient, installed and checked against \
2225                 Lean's canonical signature."
2226            ),
2227            Decl::Inductive { members } if members.len() > 1 => format!(
2228                "Mutual inductive block {}.",
2229                members.iter().map(|m| format!("`{}`", display_key(m))).collect::<Vec<_>>().join(", ")
2230            ),
2231            Decl::Inductive { members } if members.iter().any(|m| self.ctx.struct_info(m).is_some()) => format!(
2232                "Inductive `{lean}`, a structure: its projections and its η follow it, before anything that \
2233                 relies on η."
2234            ),
2235            Decl::Inductive { .. } => format!("Inductive `{lean}`."),
2236            Decl::Theorem { transparent: true, .. } => format!(
2237                "Theorem `{lean}`: its checked proof is written; like Lean's kernel, LOGOS unfolds it."
2238            ),
2239            Decl::Theorem { transparent: false, .. } => {
2240                format!("Opaque constant `{lean}`: its checked value is written and never unfolds.")
2241            }
2242            Decl::Definition { key, .. } if key.starts_with(HELPER_PREFIX) => format!(
2243                "Shared subterm `{key}` of `{}`, introduced by the Lean reader to keep its sharing; a checked definition.",
2244                self.owners.get(&unit).cloned().unwrap_or_default()
2245            ),
2246            Decl::Definition { key, .. } if is_derived_recursor(self.ctx, key) => {
2247                format!("Recursor `{lean}`, derived by LOGOS from its inductive and checked.")
2248            }
2249            Decl::Definition { .. } => format!("Definition `{lean}`."),
2250            Decl::Prelude { .. } => format!("`{lean}` is the LOGOS prelude's own."),
2251        }
2252    }
2253
2254    /// The document: a header, then every block under its section heading, each with a prose line
2255    /// naming the Lean declaration it carries. Returns the source and each unit's line range.
2256    fn assemble(
2257        &self,
2258        blocks: &[(usize, String, Form)],
2259    ) -> Result<(String, BTreeMap<usize, (usize, usize)>)> {
2260        let mut out = String::new();
2261        out.push_str(&self.header());
2262        let mut section = None;
2263        let mut lines = BTreeMap::new();
2264        for (unit, text, _) in blocks {
2265            let s = if *unit == self.closure.target {
2266                4
2267            } else {
2268                rank(&self.closure.decls[*unit]) as usize
2269            };
2270            if section != Some(s) {
2271                out.push_str(&format!("\n/-! ## {} -/\n", SECTIONS[s]));
2272                section = Some(s);
2273            }
2274            out.push_str(&format!("\n/-- {} -/\n", prose(&self.describe(*unit))));
2275            let first = out.lines().count() + 1;
2276            out.push_str(text);
2277            out.push('\n');
2278            let last = out.lines().count();
2279            lines.insert(*unit, (first, last));
2280        }
2281        Ok((out, lines))
2282    }
2283
2284    /// The declarations whose binder annotations the import recorded and the document does not
2285    /// write, by LOGOS name.
2286    fn annotated(&self) -> Vec<String> {
2287        let mut out: Vec<String> = self
2288            .closure
2289            .unit_of
2290            .keys()
2291            .filter(|key| {
2292                self.ctx.binder_kinds(key).is_some_and(|kinds| {
2293                    kinds
2294                        .iter()
2295                        .any(|k| *k != crate::elaborate::ParamKind::Explicit)
2296                })
2297            })
2298            .filter_map(|key| self.logos.get(key).cloned())
2299            .collect();
2300        out.sort();
2301        out
2302    }
2303
2304    fn losses(&self) -> Vec<(String, String)> {
2305        let mut out: Vec<(String, String)> = self
2306            .annotated()
2307            .into_iter()
2308            .map(|name| {
2309                (
2310                    "binder_annotations".to_string(),
2311                    format!("`{name}`: implicit, strict-implicit and instance binders are written as explicit ones"),
2312                )
2313            })
2314            .collect();
2315        out.extend(
2316            self.rename_lines()
2317                .into_iter()
2318                .map(|line| ("binder_renamed".to_string(), line)),
2319        );
2320        out
2321    }
2322
2323    /// One line per Lean binder name the document could not keep, each distinct rename once.
2324    fn rename_lines(&self) -> Vec<String> {
2325        let mut seen = BTreeSet::new();
2326        self.renames
2327            .borrow()
2328            .iter()
2329            .filter(|entry| seen.insert((*entry).clone()))
2330            .map(|(declared, (lean, written, reason))| {
2331                format!(
2332                    "`{declared}`: the Lean binder `{lean}` is written `{written}` because {}",
2333                    reason.as_str()
2334                )
2335            })
2336            .collect()
2337    }
2338
2339    fn header(&self) -> String {
2340        let target = self.closure.target_key.as_str();
2341        let lean = display_key(target);
2342        let written = self
2343            .closure
2344            .decls
2345            .iter()
2346            .filter(|d| !matches!(d, Decl::Prelude { .. }))
2347            .count();
2348        let axioms: Vec<String> = self
2349            .closure
2350            .decls
2351            .iter()
2352            .filter_map(|d| match d {
2353                Decl::Axiom { key, .. } => Some(format!(
2354                    "`{}` (Lean `{}`)",
2355                    self.logos[key],
2356                    display_key(key)
2357                )),
2358                _ => None,
2359            })
2360            .collect();
2361        let mut text = String::new();
2362        text.push_str(&format!(
2363            "# LOGOS source for the Lean declaration `{lean}`\n\n"
2364        ));
2365        text.push_str(&format!(
2366            "The declaration `{}` and the {} declarations it depends on, written in LOGOS from a checked \
2367             import. Where a literate English form states a declaration exactly it is written that way; \
2368             every other declaration is written in the kernel vernacular. Before this document was \
2369             published it was read back in a fresh LOGOS context and every declaration was compared with \
2370             the imported one.\n\n",
2371            self.logos[target],
2372            written.saturating_sub(1)
2373        ));
2374        text.push_str("## Assumptions\n\n");
2375        if axioms.is_empty() {
2376            text.push_str("Nothing below is assumed: every declaration is a definition, a type or a checked proof.\n\n");
2377        } else {
2378            text.push_str("These axioms are assumed, each written below as an `Axiom`:\n\n");
2379            for a in &axioms {
2380                text.push_str(&format!("- {a}\n"));
2381            }
2382            text.push('\n');
2383        }
2384        text.push_str("## Names\n\n");
2385        match &self.relocated_under {
2386            Some(root) => text.push_str(&format!(
2387                "Lean names are kept. A Lean root namespace the LOGOS prelude already uses is placed, with \
2388                 everything inside it, under `{root}`; Lean `Nat` is the prelude's own `Nat`, `Zero` and \
2389                 `Succ`. The correspondence index lists every pair.\n\n"
2390            )),
2391            None => text.push_str(
2392                "Lean names are kept; Lean `Nat` is the prelude's own `Nat`, `Zero` and `Succ`. The \
2393                 correspondence index lists every pair.\n\n",
2394            ),
2395        }
2396        let renamed = self.rename_lines();
2397        if self.hygienic.get() {
2398            text.push_str(
2399                "A hygienic Lean binder, which Lean shows as inaccessible (`a✝`), is written under its own \
2400                 name (`a`), as Lean's `eraseMacroScopes` reads it.\n\n",
2401            );
2402        }
2403        if renamed.is_empty() {
2404            text.push_str("Every binder the text names keeps its Lean name.\n\n");
2405        } else {
2406            text.push_str(
2407                "Every binder the text names keeps its Lean name, except where that name would read as \
2408                 something else — a constant this document writes or the LOGOS prelude declares, an enclosing \
2409                 binder, or syntax; then the first free `name1`, `name2`, … is written:\n\n",
2410            );
2411            for line in &renamed {
2412                text.push_str(&format!("- {line}\n"));
2413            }
2414            text.push('\n');
2415        }
2416        // The text depends on the checked declarations alone, so a replay that rebuilds the environment
2417        // from a certificate writes the same bytes; which declarations had annotated binders is import
2418        // metadata, reported in `EmittedDocument::losses` rather than here.
2419        text.push_str("## Not carried\n\n");
2420        text.push_str(
2421            "Binder annotations (implicit, strict-implicit and instance binders) are not written: every \
2422             application below is fully explicit, exactly as the kernel checked it.\n\n",
2423        );
2424        text.push_str(
2425            "A binder nothing refers to is written as an arrow (`A -> B`) and carries no name, as Lean \
2426             itself displays it.\n",
2427        );
2428        format!("/-!\n{}-/\n", prose(&text))
2429    }
2430
2431    fn correspondence(
2432        &self,
2433        order: &[usize],
2434        forms: &BTreeMap<usize, Form>,
2435        lines: &BTreeMap<usize, (usize, usize)>,
2436    ) -> Vec<Correspondence> {
2437        let mut out = Vec::new();
2438        for &unit in order {
2439            let decl = &self.closure.decls[unit];
2440            let form = forms.get(&unit).copied().unwrap_or(Form::Prelude);
2441            let span = lines.get(&unit).copied();
2442            let levels = self.levels[unit].clone();
2443            let entry = |key: &str, kind: DeclarationKind, origin: Origin| Correspondence {
2444                lean: display_key(key),
2445                importer_key: key.to_string(),
2446                logos: self.logos[key].clone(),
2447                kind,
2448                origin,
2449                form,
2450                levels: levels.clone(),
2451                lines: span,
2452            };
2453            match decl {
2454                Decl::Prelude { members } | Decl::Inductive { members } => {
2455                    let origin = if matches!(decl, Decl::Prelude { .. }) {
2456                        Origin::Prelude
2457                    } else {
2458                        Origin::Authored
2459                    };
2460                    for member in members {
2461                        out.push(entry(member, DeclarationKind::Inductive, origin.clone()));
2462                        for (ctor, _) in self.ctx.get_constructors(member) {
2463                            out.push(entry(ctor, DeclarationKind::Constructor, origin.clone()));
2464                        }
2465                        for projection in projections_of(self.ctx, member) {
2466                            out.push(entry(
2467                                &projection,
2468                                DeclarationKind::Definition,
2469                                Origin::Synthesized,
2470                            ));
2471                        }
2472                    }
2473                }
2474                Decl::Definition { key, .. } => {
2475                    let origin = if key.starts_with(HELPER_PREFIX) {
2476                        Origin::Helper {
2477                            of: self.owners.get(&unit).cloned().unwrap_or_default(),
2478                        }
2479                    } else if is_derived_recursor(self.ctx, key) {
2480                        Origin::Synthesized
2481                    } else {
2482                        Origin::Authored
2483                    };
2484                    let mut e = entry(key, DeclarationKind::Definition, origin);
2485                    if key.starts_with(HELPER_PREFIX) {
2486                        e.lean = key.clone();
2487                    }
2488                    out.push(e);
2489                }
2490                Decl::Theorem {
2491                    key, transparent, ..
2492                } => {
2493                    let kind = if *transparent {
2494                        DeclarationKind::Theorem
2495                    } else {
2496                        DeclarationKind::Opaque
2497                    };
2498                    out.push(entry(key, kind, Origin::Authored))
2499                }
2500                Decl::Axiom { key, .. } => {
2501                    out.push(entry(key, DeclarationKind::Axiom, Origin::Authored))
2502                }
2503                Decl::Quotient { key, .. } => {
2504                    out.push(entry(key, DeclarationKind::Quotient, Origin::Authored))
2505                }
2506            }
2507        }
2508        out
2509    }
2510}
2511
2512/// A Lean recursor: the importer registers `I.rec` from the kernel's own derivation, never from the
2513/// export's rules.
2514fn is_derived_recursor(ctx: &Context, key: &str) -> bool {
2515    match key.strip_suffix("/s:\"rec\"") {
2516        Some(owner) => ctx.is_inductive(owner),
2517        None => false,
2518    }
2519}
2520
2521/// The first non-helper unit a helper is reached from, walking referrers.
2522fn owner_of(unit: usize, referrers: &[BTreeSet<usize>], closure: &Closure) -> Option<usize> {
2523    let mut seen = BTreeSet::new();
2524    let mut frontier: Vec<usize> = vec![unit];
2525    while let Some(u) = frontier.pop() {
2526        for &r in &referrers[u] {
2527            if !seen.insert(r) {
2528                continue;
2529            }
2530            if closure.decls[r].key().starts_with(HELPER_PREFIX) {
2531                frontier.push(r);
2532            } else {
2533                return Some(r);
2534            }
2535        }
2536    }
2537    None
2538}
2539
2540/// Universe parameter names: the Lean name's last component when it is a plain identifier, made
2541/// distinct.
2542fn level_names(levels: &[String]) -> Vec<(String, String)> {
2543    let mut used: HashSet<String> = HashSet::new();
2544    let mut out = Vec::with_capacity(levels.len());
2545    for level in levels {
2546        let base = match names::lean_name(level).and_then(|p| p.last().cloned()) {
2547            Some(LeanPart::Str(s)) if terms::is_plain_identifier(&s) => s,
2548            Some(LeanPart::Num(n)) => format!("u{n}"),
2549            _ => "u".to_string(),
2550        };
2551        let name = std::iter::once(base.clone())
2552            .chain((1usize..).map(|i| format!("{base}{i}")))
2553            .find(|n| !used.contains(n))
2554            .expect("an unbounded sequence of candidates has a free one");
2555        used.insert(name.clone());
2556        out.push((level.clone(), name));
2557    }
2558    out
2559}
2560
2561/// The namespace scope a declaration named `declared` elaborates in.
2562fn declaration_scope(declared: &str) -> Namespaces {
2563    let mut ns = Namespaces::new();
2564    if let Some(name) = Name::parse(declared) {
2565        ns.enter_declaration(&name, 0);
2566    }
2567    ns
2568}
2569
2570/// The shortest written name that resolves to `key` in `scope`, or its `_root_` form.
2571fn written_reference(
2572    scope: &Namespaces,
2573    known: &dyn Fn(&str) -> bool,
2574    key: &str,
2575) -> Option<String> {
2576    let parts: Vec<String> = key.split('.').map(str::to_string).collect();
2577    for take in 1..=parts.len() {
2578        let candidate = Name::from_parts(parts[parts.len() - take..].to_vec())?;
2579        if let Resolution::Found(found) = scope.resolve(&candidate, known) {
2580            if found == key {
2581                return Some(candidate.key());
2582            }
2583        }
2584    }
2585    let rooted = Name::from_parts(
2586        std::iter::once(crate::interface::surface::ROOT_ESCAPE.to_string())
2587            .chain(parts)
2588            .collect(),
2589    )?;
2590    match scope.resolve(&rooted, known) {
2591        Resolution::Found(found) if found == key => Some(rooted.key()),
2592        _ => None,
2593    }
2594}
2595
2596/// A constructor's name as written inside its inductive's declaration: relative to the namespace
2597/// the inductive is declared in.
2598fn relative_to(key: &str, enclosing: &[&str]) -> String {
2599    let parts: Vec<&str> = key.split('.').collect();
2600    if parts.len() > enclosing.len() && parts[..enclosing.len()] == *enclosing {
2601        parts[enclosing.len()..].join(".")
2602    } else {
2603        format!("{}.{key}", crate::interface::surface::ROOT_ESCAPE)
2604    }
2605}
2606
2607/// Prose made safe inside a `/-! … -/` or `/-- … -/` block: no nested opener or closer.
2608fn prose(text: &str) -> String {
2609    text.replace("/-", "/\u{2010}").replace("-/", "\u{2010}/")
2610}
2611
2612/// A unit as it will be printed.
2613enum Written {
2614    Definition {
2615        name: String,
2616        levels: Vec<String>,
2617        ty: Term,
2618        body: Term,
2619    },
2620    Theorem {
2621        name: String,
2622        transparent: bool,
2623        levels: Vec<String>,
2624        ty: Term,
2625        proof: Term,
2626    },
2627    Axiom {
2628        name: String,
2629        levels: Vec<String>,
2630        ty: Term,
2631    },
2632    Quotient {
2633        name: String,
2634        kind: crate::quotient::Kind,
2635        levels: Vec<String>,
2636        equality: Option<String>,
2637    },
2638    Inductive(Vec<WrittenInductive>),
2639}
2640
2641struct WrittenInductive {
2642    name: String,
2643    levels: Vec<String>,
2644    params: Vec<(String, Term)>,
2645    sort: Term,
2646    constructors: Vec<(String, Term)>,
2647    /// Constructors carry their full parameter telescope.
2648    full: bool,
2649    /// For a structure, its projections as written: name, type, body.
2650    structure: Option<Vec<(String, Term, Term)>>,
2651    result_heads: Vec<String>,
2652}
2653
2654impl Written {
2655    fn logos_name(&self) -> &str {
2656        match self {
2657            Written::Definition { name, .. }
2658            | Written::Theorem { name, .. }
2659            | Written::Axiom { name, .. }
2660            | Written::Quotient { name, .. } => name,
2661            Written::Inductive(members) => &members[0].name,
2662        }
2663    }
2664}