Skip to main content

logicaffeine_kernel/interface/
surface.rs

1//! The SURFACE environment — user-declared notation, fixity and macros.
2//!
3//! A mathematician coins notation constantly, and the vernacular had exactly ONE infix table,
4//! hard-coded, with no extension point. This module is that extension point, and the shape of it
5//! is the whole design:
6//!
7//! * **A parser-extension command declares into the SURFACE, never into the namespace.** The
8//!   kernel context holds declarations; this holds ways of spelling them. `Notation "a ⊕ b" := …`
9//!   adds no global — not the token, not the command word — because a flat namespace with one
10//!   phantom per notation is a collision waiting to happen.
11//! * **Two stages, because they are two different things.** A macro is a PATTERN REWRITE and can
12//!   be recursive, so it runs as a source-to-source pre-pass to a fixpoint. A notation is a fixed
13//!   shape with a PRECEDENCE, so it is a layer in the term parser. Lean stages them the same way
14//!   and for the same reason.
15//! * **Scope is the section stack.** A declaration lives in the innermost open section and dies
16//!   with it, so one token can mean two things in two sections without either shadowing the other.
17//!   `Local` is the explicit spelling of what already happens.
18//! * **A namespace is the purest case of "a spelling, not a declaration".** `Namespace`/`End`/`Open`
19//!   change which constant a short name DENOTES and add nothing to the context. Lean agrees: its
20//!   kernel `Environment` maps absolute names to constants and the namespace scope lives in the
21//!   elaborator, with `resolveGlobalName` running to completion before anything reaches the kernel.
22//!   So it lives here and the resolution runs in the `Repl`, which is why the kernel can stay a flat
23//!   map of absolute keys and know nothing about any of it.
24//! * **Hygiene is not optional.** A template's own binders are renamed to names that do not occur
25//!   anywhere in the expansion before its arguments are spliced in, so a template binder can never
26//!   capture a call-site name. Without it `Macro "twiceIt $e" := (fun x : Int => … $e …)` applied
27//!   at a call site that also binds `x` computes a silently wrong answer — not a type error.
28
29use std::collections::{HashMap, HashSet};
30
31use super::error::ParseError;
32use super::term_parser::{scan_name, BinderKind, BinderSurface, TermParser};
33use crate::Term;
34
35/// The precedence an infix notation gets when it does not declare one, and the associativity that
36/// comes with it. Two notations at the same level then nest to the RIGHT, so `a ⊕ b ⊗ c` groups as
37/// `a ⊕ (b ⊗ c)` — the reading a reader gives an unparenthesised chain of unfamiliar operators.
38const DEFAULT_PRECEDENCE: u32 = 65;
39
40/// The precedence a PREFIX or POSTFIX notation gets. Neither has a second operand to compete for,
41/// so both bind as tightly as anything can: `1 \u{22c4} 7 \u{203c}` applies the postfix to `7`, not
42/// to the whole spine.
43const TIGHT_PRECEDENCE: u32 = 1000;
44
45/// How many rewrite steps a macro expansion may take before it is declared non-terminating. A
46/// recursive `macro_rules` that never reaches its base case must fail LOUDLY rather than hang.
47const MAX_MACRO_STEPS: usize = 1024;
48
49/// One piece of a notation or macro pattern: a literal token to match, or a named hole to capture.
50#[derive(Debug, Clone, PartialEq)]
51enum Piece {
52    Tok(String),
53    Hole(String),
54}
55
56/// Where a notation's holes sit relative to its tokens. Decided by the pattern, not declared.
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58enum Shape {
59    /// `a ⊕ b` — a hole, tokens, a hole. Parsed in the operator loop, by precedence.
60    Infix,
61    /// `∽ a` — leading token. Parsed at atom position.
62    Prefix,
63    /// `a ‼` — trailing token. Parsed in the operator loop, tighter than any infix.
64    Postfix,
65    /// `⟦ n ⟧` — tokens on both ends. Parsed at atom position; brackets need no precedence.
66    Closed,
67}
68
69/// Which way a chain of one operator nests.
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum Assoc {
72    /// `a ⋄ b ⋄ c` = `f (f a b) c`.
73    Left,
74    /// `a ▷ b ▷ c` = `g a (g b c)`.
75    Right,
76}
77
78/// A declaration that extends the SURFACE rather than the context. Emitted by the command parser,
79/// applied by the REPL — so the one place a parser extension can land is the surface environment.
80#[derive(Debug, Clone)]
81pub enum SurfaceDecl {
82    /// `Notation "a ⊕ b" := ((myOp a) b).` and the fixity spellings, which are the same thing with
83    /// the shape and associativity given by the command word instead of by the pattern.
84    Notation {
85        pattern: String,
86        template: String,
87        precedence: Option<u32>,
88        assoc: Assoc,
89        /// For `Infixl`/`Infixr`/`Prefix`/`Postfix`, the operator's own spelling: the pattern is
90        /// just the token and the holes are implied.
91        operator_form: Option<OperatorForm>,
92    },
93    /// `Macro "twiceIt $e" := …` / `MacroRules "nested![$x, $xs]" := …`.
94    Macro { pattern: String, template: String },
95    /// `Section First.` — or a bare `Section.`, which is Lean's ANONYMOUS section
96    /// (`section` with no name), the ordinary way to scope a `local` without inventing a label for
97    /// it. `None` is that form.
98    OpenSection(Option<String>),
99    /// `End First.` — and, when the innermost open scope is a NAMESPACE of that name, the close of
100    /// that namespace instead. Lean spells both with one keyword and decides by what is open;
101    /// deciding it here rather than in the parser is what lets the parser stay text-only.
102    ///
103    /// `None` is the bare `End.` that closes an anonymous section. It closes ONLY an anonymous
104    /// one: lean 4.14.0 answers `namespace N … end` with `invalid 'end', name is missing
105    /// (expected N)`, and a bare `End` that silently closed a named scope would leave every later
106    /// declaration somewhere nobody wrote.
107    CloseSection(Option<String>),
108    /// `Namespace Outer.Inner.` — a namespace declarations are written INTO and short names resolve
109    /// against. Nothing is declared: like every other surface declaration this changes how a name
110    /// is spelled and read, never what exists.
111    OpenNamespace(Name),
112}
113
114/// The shape a fixity command implies for its operator.
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub enum OperatorForm {
117    Infix,
118    Prefix,
119    Postfix,
120}
121
122/// A notation in scope.
123#[derive(Debug, Clone)]
124struct Notation {
125    pieces: Vec<Piece>,
126    shape: Shape,
127    precedence: u32,
128    assoc: Assoc,
129    /// The expansion, parsed once at declaration time with the holes bound, so an invocation is a
130    /// SUBSTITUTION and not a re-parse of spliced text.
131    template: Term,
132}
133
134/// A macro rule in scope. Its template stays TEXT: an alternative may invoke the macro it is
135/// defining, and that call can only be expanded after its arguments are known.
136#[derive(Debug, Clone)]
137struct MacroRule {
138    pieces: Vec<Piece>,
139    template: String,
140}
141
142/// One section's worth of surface declarations.
143#[derive(Debug, Default)]
144struct Scope {
145    name: Option<String>,
146    notations: Vec<Notation>,
147    macros: Vec<MacroRule>,
148}
149
150/// The surface environment: a stack of scopes, innermost last.
151#[derive(Debug)]
152pub struct Surface {
153    scopes: Vec<Scope>,
154    /// Monotonic, so two expansions in one term cannot pick the same hygienic name. Atomic rather
155    /// than a `Cell` so a `Surface` can be read from the deep-stack thread a long term is parsed on.
156    fresh: std::sync::atomic::AtomicU64,
157    /// The NAMESPACE scope: what is current and what is open. It sits beside the notation for the
158    /// same reason the notation sits beside the kernel context — a way of spelling a name is not a
159    /// declaration, and namespace-relative reference is the purest case of that.
160    namespaces: Namespaces,
161    /// Per-declaration parameter surface — every entry has at least one DEFAULT VALUE — keyed by
162    /// the absolute key the kernel context is indexed at.
163    ///
164    /// ★ A DEFAULT IS A SPELLING, WHICH IS WHY IT LIVES HERE. `(m : Int := 3)` does not change
165    /// what `withDefault` IS: its type is `Int -> Int -> Int` either way and exactly the same
166    /// terms inhabit it. What it changes is how an application may be WRITTEN — `withDefault 1`
167    /// is a spelling of `withDefault 1 3` — and a way of spelling something is what this module
168    /// holds. Storing it in the kernel context would make a calling convention part of a
169    /// declaration's identity, which is the same mistake as minting a global per notation.
170    ///
171    /// Empty for every declaration that writes no default, so the map is empty in every session
172    /// that never writes one and [`fill_defaults`] is skipped outright.
173    defaults: HashMap<String, Vec<BinderSurface>>,
174}
175
176impl Default for Surface {
177    fn default() -> Self {
178        Self::new()
179    }
180}
181
182impl Surface {
183    /// A surface with nothing declared. The parse path it produces is byte-identical to having no
184    /// surface at all, which is what keeps the extension point free when nobody uses it.
185    pub fn new() -> Self {
186        Self {
187            scopes: vec![Scope::default()],
188            fresh: std::sync::atomic::AtomicU64::new(0),
189            namespaces: Namespaces::new(),
190            defaults: HashMap::new(),
191        }
192    }
193
194    /// Record what `key`'s declaration said about its parameters, for as long as `key` is
195    /// declared.
196    ///
197    /// Only a telescope carrying a DEFAULT is kept: nothing else is a spelling, and keeping the
198    /// rest would make [`Surface::fills_defaults`] true for every session and cost a term walk per
199    /// command for nothing. A redeclaration REPLACES the entry, and a redeclaration that dropped
200    /// the default REMOVES it — a stale default would fill an argument the current declaration
201    /// never offered.
202    pub(super) fn record_defaults(&mut self, key: &str, binders: Vec<BinderSurface>) {
203        if binders.iter().any(|b| b.default.is_some()) {
204            self.defaults.insert(key.to_string(), binders);
205        } else {
206            self.defaults.remove(key);
207        }
208    }
209
210    /// `key`'s parameter surface, if any parameter of it carries a default.
211    pub(super) fn defaults_of(&self, key: &str) -> Option<&[BinderSurface]> {
212        self.defaults.get(key).map(|b| b.as_slice())
213    }
214
215    /// Has this session declared ANY default at all? The guard that keeps [`fill_defaults`] off
216    /// the path of every session that has not.
217    pub(super) fn fills_defaults(&self) -> bool {
218        !self.defaults.is_empty()
219    }
220
221    /// The namespace scope: what is current, what is open.
222    pub fn namespaces(&self) -> &Namespaces {
223        &self.namespaces
224    }
225
226    /// The namespace scope, to push and wind back a SCOPED opening — `Open … in <command>`, and a
227    /// declaration whose own name names the namespace it is written into.
228    pub fn namespaces_mut(&mut self) -> &mut Namespaces {
229        &mut self.namespaces
230    }
231
232    /// Enter the scope a DECLARATION named `declared` elaborates in, and give back the absolute key
233    /// it declares. See [`Namespaces::enter_declaration`]; this is the door, because only the surface
234    /// knows how deep the section stack is, and that is what orders the two scope stacks.
235    pub fn enter_declaration(&mut self, declared: &Name) -> (String, ScopeMark) {
236        let depth = self.scopes.len();
237        self.namespaces.enter_declaration(declared, depth)
238    }
239
240    /// Is anything declared? A surface with no notations and no macros is skipped entirely.
241    pub fn is_empty(&self) -> bool {
242        self.scopes
243            .iter()
244            .all(|s| s.notations.is_empty() && s.macros.is_empty())
245    }
246
247    /// How many SECTIONS are open. The root scope is never pushed and never popped, so it is not
248    /// one of them.
249    pub fn section_depth(&self) -> usize {
250        self.scopes.len() - 1
251    }
252
253    /// The open sections, outermost first \u{2014} what a document must close before it ends. An
254    /// anonymous section is named AS one rather than as the empty string, so an unbalanced
255    /// document reports something a reader can act on.
256    pub fn section_names(&self) -> Vec<String> {
257        self.scopes
258            .iter()
259            .skip(1)
260            .map(|s| {
261                s.name
262                    .clone()
263                    .unwrap_or_else(|| ANONYMOUS_SECTION.to_string())
264            })
265            .collect()
266    }
267
268    /// Close every section opened since `depth`. Only ever SHRINKS: a section already closed took
269    /// its notations with it and cannot be brought back, which is why an over-closing document is
270    /// reported rather than silently repaired.
271    pub fn close_sections_to(&mut self, depth: usize) {
272        self.scopes.truncate(depth + 1);
273    }
274
275    /// Apply one surface declaration.
276    pub fn declare(&mut self, decl: SurfaceDecl) -> Result<(), ParseError> {
277        match decl {
278            SurfaceDecl::OpenSection(name) => {
279                self.scopes.push(Scope {
280                    name,
281                    ..Scope::default()
282                });
283                Ok(())
284            }
285            SurfaceDecl::OpenNamespace(name) => {
286                let depth = self.scopes.len();
287                self.namespaces.enter(&name, depth);
288                Ok(())
289            }
290            SurfaceDecl::CloseSection(name) => {
291                // ★ ONE `End` KEYWORD, AND THE INNERMOST OPEN SCOPE DECIDES — Lean's rule, and it
292                // has to be asked in that ORDER. This asked the namespace stack FIRST, which closed
293                // a namespace straight THROUGH an open section: `Namespace N. Section S. End N.`
294                // popped `N` and left `S` open, so every later declaration was keyed outside the
295                // namespace its author was writing in and the file still checked. lean 4.14.0
296                // refuses that exact program with `invalid 'end', name mismatch (expected S)`.
297                let written = end_spelling(&name);
298                if let Some(open) = self.namespaces.innermost_scope(self.scopes.len()).cloned() {
299                    // A NAMESPACE is the innermost open scope: only its own name closes it, and a
300                    // bare `End` does not (lean: "name is missing (expected N)").
301                    if name.as_deref().and_then(Name::parse).as_ref() == Some(&open) {
302                        self.namespaces.leave(&open);
303                        return Ok(());
304                    }
305                    return Err(ParseError::Expected {
306                        expected: format!(
307                            "`End {open}` — the innermost open scope is the namespace {open}"
308                        ),
309                        found: written,
310                    });
311                }
312                if self.scopes.len() <= 1 {
313                    return Err(ParseError::Expected {
314                        expected: "an open section or namespace".to_string(),
315                        found: written,
316                    });
317                }
318                let open = self
319                    .scopes
320                    .last()
321                    .expect("the length was checked")
322                    .name
323                    .clone();
324                if open == name {
325                    self.scopes.pop();
326                    return Ok(());
327                }
328                Err(ParseError::Expected {
329                    expected: match &open {
330                        Some(open) => format!("`End {open}` — sections close innermost first"),
331                        None => format!(
332                            "a bare `End` — the innermost open scope is {ANONYMOUS_SECTION}"
333                        ),
334                    },
335                    found: written,
336                })
337            }
338            SurfaceDecl::Macro { pattern, template } => {
339                let pieces = macro_pattern(&pattern)?;
340                if !matches!(pieces.first(), Some(Piece::Tok(_))) {
341                    return Err(ParseError::Expected {
342                        expected: "a macro pattern beginning with a literal token".to_string(),
343                        found: pattern,
344                    });
345                }
346                // A LATER block is tried FIRST: `macro_rules` prepends its alternatives, so the
347                // most recent declaration wins. Innermost-first lookup already gives that inside a
348                // scope, since the search runs back to front.
349                self.scopes
350                    .last_mut()
351                    .expect("the root scope is never popped")
352                    .macros
353                    .push(MacroRule { pieces, template });
354                Ok(())
355            }
356            SurfaceDecl::Notation {
357                pattern,
358                template,
359                precedence,
360                assoc,
361                operator_form,
362            } => {
363                let pieces = match operator_form {
364                    None => notation_pattern(&pattern)?,
365                    Some(OperatorForm::Infix) => vec![
366                        Piece::Hole("a".to_string()),
367                        Piece::Tok(pattern.trim().to_string()),
368                        Piece::Hole("b".to_string()),
369                    ],
370                    Some(OperatorForm::Prefix) => vec![
371                        Piece::Tok(pattern.trim().to_string()),
372                        Piece::Hole("a".to_string()),
373                    ],
374                    Some(OperatorForm::Postfix) => vec![
375                        Piece::Hole("a".to_string()),
376                        Piece::Tok(pattern.trim().to_string()),
377                    ],
378                };
379                let shape = shape_of(&pieces)?;
380                let holes: Vec<String> = pieces
381                    .iter()
382                    .filter_map(|p| match p {
383                        Piece::Hole(h) => Some(h.clone()),
384                        Piece::Tok(_) => None,
385                    })
386                    .collect();
387
388                // An operator form names a FUNCTION; the expansion is that function applied to the
389                // holes in order. A `Notation` writes its expansion out.
390                let template = match operator_form {
391                    None => TermParser::parse_template(&template, &holes)?,
392                    Some(_) => {
393                        let f = TermParser::parse_template(&template, &[])?;
394                        holes.iter().fold(f, |acc, h| {
395                            Term::App(Box::new(acc), Box::new(Term::Var(h.clone())))
396                        })
397                    }
398                };
399
400                self.scopes
401                    .last_mut()
402                    .expect("the root scope is never popped")
403                    .notations
404                    .push(Notation {
405                        pieces,
406                        shape,
407                        precedence: precedence.unwrap_or(match shape {
408                            Shape::Prefix | Shape::Postfix | Shape::Closed => TIGHT_PRECEDENCE,
409                            Shape::Infix => DEFAULT_PRECEDENCE,
410                        }),
411                        assoc,
412                        template,
413                    });
414                Ok(())
415            }
416        }
417    }
418
419    /// The notations in scope, innermost section first and most recent declaration first — so the
420    /// same token may mean one thing in one section and another thing in the next.
421    fn notations(&self) -> impl Iterator<Item = &Notation> {
422        self.scopes
423            .iter()
424            .rev()
425            .flat_map(|s| s.notations.iter().rev())
426    }
427
428    /// The macro rules in scope, in the order they must be TRIED: a later block's alternatives
429    /// come first.
430    fn macros(&self) -> impl Iterator<Item = &MacroRule> {
431        self.scopes.iter().rev().flat_map(|s| s.macros.iter().rev())
432    }
433
434    // ========================================================================================
435    // STAGE 1 — MACRO EXPANSION (source to source, to a fixpoint)
436    // ========================================================================================
437
438    /// Rewrite every macro invocation in `text` until none remains.
439    ///
440    /// Text in, text out, because an alternative may invoke the macro it is defining
441    /// (`nested![$x, $xs]` expands to a `mycons` applied to `nested![$xs]`) and that inner call
442    /// can only be matched once its argument is known. The step budget is a hard backstop: a
443    /// recursive rule that never reaches its base case FAILS rather than hangs.
444    pub fn expand_macros(&self, text: &str) -> Result<String, ParseError> {
445        if self.scopes.iter().all(|s| s.macros.is_empty()) {
446            return Ok(text.to_string());
447        }
448        let mut current = text.to_string();
449        let mut steps = 0usize;
450        loop {
451            match self.expand_once(&current)? {
452                None => return Ok(current),
453                Some(next) => {
454                    steps += 1;
455                    if steps > MAX_MACRO_STEPS {
456                        return Err(ParseError::MacroDepth {
457                            limit: MAX_MACRO_STEPS,
458                        });
459                    }
460                    current = next;
461                }
462            }
463        }
464    }
465
466    /// Rewrite the LEFTMOST macro invocation, or `None` if there is none.
467    fn expand_once(&self, text: &str) -> Result<Option<String>, ParseError> {
468        let mut at = 0usize;
469        let mut in_string = false;
470        while at < text.len() {
471            if !text.is_char_boundary(at) {
472                at += 1;
473                continue;
474            }
475            // A string literal is DATA. Rewriting a macro head inside one would change a value
476            // rather than a spelling, which is the one thing an expander must never do.
477            if text.as_bytes()[at] == b'"' {
478                in_string = !in_string;
479                at += 1;
480                continue;
481            }
482            if in_string {
483                at += 1;
484                continue;
485            }
486            // A macro head is a token, so it may only start where a token may start.
487            if at > 0 && is_word_byte(text.as_bytes()[at - 1]) && is_word_start(&text[at..]) {
488                at += 1;
489                continue;
490            }
491            for rule in self.macros() {
492                if let Some((end, bindings)) = match_pieces(&rule.pieces, text, at) {
493                    let body = self.instantiate_text(&rule.template, &bindings);
494                    let mut out = String::with_capacity(text.len() + body.len());
495                    out.push_str(&text[..at]);
496                    out.push_str(&body);
497                    out.push_str(&text[end..]);
498                    return Ok(Some(out));
499                }
500            }
501            at += 1;
502        }
503        Ok(None)
504    }
505
506    /// Splice captured arguments into a macro template, hygienically.
507    ///
508    /// The template's OWN binders are renamed first, to names that occur nowhere in the template
509    /// or in any argument, so the splice that follows cannot capture a call-site name.
510    fn instantiate_text(&self, template: &str, bindings: &HashMap<String, String>) -> String {
511        let mut avoid: HashSet<String> = identifiers_in(template);
512        for v in bindings.values() {
513            avoid.extend(identifiers_in(v));
514        }
515        let renamed = self.freshen_text_binders(template, &avoid);
516        substitute_holes_in_text(&renamed, bindings)
517    }
518
519    /// Rename every binder the template itself introduces to a name nothing else uses.
520    fn freshen_text_binders(&self, template: &str, avoid: &HashSet<String>) -> String {
521        let bound = text_binder_names(template);
522        if bound.is_empty() {
523            return template.to_string();
524        }
525        let mut map = HashMap::new();
526        for name in bound {
527            map.insert(name.clone(), self.fresh_name(&name, avoid));
528        }
529        rename_identifiers_in_text(template, &map)
530    }
531
532    /// A name that is a legal identifier and occurs nowhere in `avoid`.
533    fn fresh_name(&self, base: &str, avoid: &HashSet<String>) -> String {
534        loop {
535            let n = self
536                .fresh
537                .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
538            let candidate = format!("{base}__hyg{n}");
539            if !avoid.contains(&candidate) {
540                return candidate;
541            }
542        }
543    }
544
545    // ========================================================================================
546    // STAGE 2 — NOTATION (a layer in the term parser)
547    // ========================================================================================
548
549    /// The atom-position notation — a `Prefix` or a bracketing `Closed` form — whose leading token
550    /// matches at `text`. Searched innermost section first and most recent declaration first, so a
551    /// rebinding shadows without deleting.
552    pub(super) fn atom_notation_at<'a>(&'a self, text: &str) -> Option<NotationMatch<'a>> {
553        self.notations()
554            .filter(|n| matches!(n.shape, Shape::Prefix | Shape::Closed))
555            .find(|n| match n.pieces.first() {
556                Some(Piece::Tok(t)) => token_matches(text, t),
557                _ => false,
558            })
559            .map(|n| NotationMatch { notation: n })
560    }
561
562    /// Does a notation token END an application here?
563    ///
564    /// Any NON-INITIAL literal of a notation in scope — an infix operator, a postfix marker, a
565    /// closing bracket — stops the argument list, because it belongs to the enclosing notation
566    /// rather than to the application. A notation's OPENING token does not stop anything: it
567    /// begins an atom, so an application may still take a bracketed form as an argument.
568    pub(super) fn stops_application(&self, text: &str) -> bool {
569        self.notations().any(|n| {
570            n.pieces.iter().skip(1).any(|p| match p {
571                Piece::Tok(t) => token_matches(text, t),
572                Piece::Hole(_) => false,
573            })
574        })
575    }
576
577    /// The operator-position notation — `Infix` or `Postfix` — whose operator token matches at
578    /// `text` and binds at least as tightly as `min_precedence`.
579    pub(super) fn operator_notation_at<'a>(
580        &'a self,
581        text: &str,
582        min_precedence: u32,
583    ) -> Option<NotationMatch<'a>> {
584        self.notations()
585            .filter(|n| matches!(n.shape, Shape::Infix | Shape::Postfix))
586            .filter(|n| n.precedence >= min_precedence)
587            .find(|n| match n.pieces.get(1) {
588                Some(Piece::Tok(t)) => token_matches(text, t),
589                _ => false,
590            })
591            .map(|n| NotationMatch { notation: n })
592    }
593}
594
595/// One notation the parser has decided to use, with the questions the parser needs to ask it.
596pub(super) struct NotationMatch<'a> {
597    notation: &'a Notation,
598}
599
600impl NotationMatch<'_> {
601    pub(super) fn is_closed(&self) -> bool {
602        self.notation.shape == Shape::Closed
603    }
604
605    /// The literal pieces, in order, and the hole names between them — what the parser consumes.
606    pub(super) fn pieces(&self) -> Vec<(Option<String>, Option<String>)> {
607        self.notation
608            .pieces
609            .iter()
610            .map(|p| match p {
611                Piece::Tok(t) => (Some(t.clone()), None),
612                Piece::Hole(h) => (None, Some(h.clone())),
613            })
614            .collect()
615    }
616
617    /// The precedence the right operand of an infix notation is parsed at: one tighter for a
618    /// left-associative operator, so a chain of it cannot re-enter on the right.
619    pub(super) fn right_precedence(&self) -> u32 {
620        match self.notation.assoc {
621            Assoc::Left => self.notation.precedence + 1,
622            Assoc::Right => self.notation.precedence,
623        }
624    }
625
626    /// Substitute the parsed arguments into the template.
627    ///
628    /// The template's binders are freshened first, so after it no binder shares a name with a hole
629    /// and the substitution that follows is capture-free by construction.
630    pub(super) fn expand(&self, surface: &Surface, args: &HashMap<String, Term>) -> Term {
631        let mut avoid: HashSet<String> = HashSet::new();
632        collect_identifiers(&self.notation.template, &mut avoid);
633        for t in args.values() {
634            collect_identifiers(t, &mut avoid);
635        }
636        let mut renames = HashMap::new();
637        let freshened = freshen_binders(&self.notation.template, surface, &avoid, &mut renames);
638        substitute_vars(&freshened, args)
639    }
640}
641
642// ============================================================================================
643// PATTERNS
644// ============================================================================================
645
646/// Read a NOTATION pattern: whitespace-separated pieces, where an identifier-shaped piece is a
647/// hole and everything else is a literal token. `"a \u{2295} b"` is two holes around an operator;
648/// `"\u{27e6} n \u{27e7}"` is a bracketing form.
649fn notation_pattern(pattern: &str) -> Result<Vec<Piece>, ParseError> {
650    let pieces: Vec<Piece> = pattern
651        .split_whitespace()
652        .map(|tok| match scan_name(tok) {
653            Some((name, len)) if len == tok.len() => Piece::Hole(name),
654            _ => Piece::Tok(tok.to_string()),
655        })
656        .collect();
657    if pieces.is_empty() {
658        return Err(ParseError::Missing("a notation pattern".to_string()));
659    }
660    if !pieces.iter().any(|p| matches!(p, Piece::Tok(_))) {
661        return Err(ParseError::Expected {
662            expected: "a notation pattern with at least one literal token".to_string(),
663            found: pattern.to_string(),
664        });
665    }
666    Ok(pieces)
667}
668
669/// Read a MACRO pattern. Holes are written `$name`; everything else is a literal token, and the
670/// tokens are NOT whitespace-separated — `nested![$x, $xs]` is `nested![`, `$x`, `,`, `$xs`, `]`,
671/// because a macro head runs straight into its opening bracket.
672fn macro_pattern(pattern: &str) -> Result<Vec<Piece>, ParseError> {
673    let mut pieces = Vec::new();
674    let mut literal = String::new();
675    let mut rest = pattern;
676    while !rest.is_empty() {
677        let c = rest.chars().next().expect("non-empty");
678        if c.is_whitespace() {
679            if !literal.is_empty() {
680                pieces.push(Piece::Tok(std::mem::take(&mut literal)));
681            }
682            rest = &rest[c.len_utf8()..];
683            continue;
684        }
685        if c == '$' {
686            if !literal.is_empty() {
687                pieces.push(Piece::Tok(std::mem::take(&mut literal)));
688            }
689            let after = &rest['$'.len_utf8()..];
690            let (name, len) = scan_name(after).ok_or_else(|| ParseError::Expected {
691                expected: "a hole name after `$`".to_string(),
692                found: after.chars().take(8).collect::<String>(),
693            })?;
694            pieces.push(Piece::Hole(format!("${name}")));
695            rest = &after[len..];
696            continue;
697        }
698        literal.push(c);
699        rest = &rest[c.len_utf8()..];
700    }
701    if !literal.is_empty() {
702        pieces.push(Piece::Tok(literal));
703    }
704    if pieces.is_empty() {
705        return Err(ParseError::Missing("a macro pattern".to_string()));
706    }
707    Ok(pieces)
708}
709
710/// Where the holes sit decides how the notation is parsed.
711fn shape_of(pieces: &[Piece]) -> Result<Shape, ParseError> {
712    let first_is_hole = matches!(pieces.first(), Some(Piece::Hole(_)));
713    let last_is_hole = matches!(pieces.last(), Some(Piece::Hole(_)));
714    Ok(match (first_is_hole, last_is_hole) {
715        (true, true) => Shape::Infix,
716        (false, true) => Shape::Prefix,
717        (true, false) => Shape::Postfix,
718        (false, false) => Shape::Closed,
719    })
720}
721
722// ============================================================================================
723// MACRO MATCHING (text level)
724// ============================================================================================
725
726/// Match `pieces` against `text` starting at `at`, returning where the match ended and what each
727/// hole captured.
728///
729/// A hole captures the text up to its FOLLOWING literal token at bracket depth zero — which is why
730/// `nested![$x, $xs]` splits `1, 2, 3` into `1` and `2, 3` rather than demanding three holes. A
731/// trailing hole captures up to the enclosing close bracket, so `(bump 5)` binds `5` and not `5)`.
732fn match_pieces(
733    pieces: &[Piece],
734    text: &str,
735    at: usize,
736) -> Option<(usize, HashMap<String, String>)> {
737    let mut pos = at;
738    let mut bindings = HashMap::new();
739    let mut i = 0usize;
740    while i < pieces.len() {
741        match &pieces[i] {
742            Piece::Tok(tok) => {
743                let skipped = pos + leading_space(&text[pos..]);
744                if !token_matches(&text[skipped..], tok) {
745                    return None;
746                }
747                pos = skipped + tok.len();
748            }
749            Piece::Hole(name) => {
750                let next_tok = match pieces.get(i + 1) {
751                    Some(Piece::Tok(t)) => Some(t.as_str()),
752                    // Two holes in a row have no separator to stop the first one.
753                    Some(Piece::Hole(_)) => return None,
754                    None => None,
755                };
756                let start = pos + leading_space(&text[pos..]);
757                match capture_end(text, start, next_tok) {
758                    Some(end) => {
759                        let captured = text[start..end].trim();
760                        if captured.is_empty() {
761                            return None;
762                        }
763                        bindings.insert(name.clone(), captured.to_string());
764                        pos = end;
765                    }
766                    // THE EMPTY TAIL. `nested![$x, $xs]` is how a comma-separated REST is written,
767                    // and a one-element list has no comma in it at all: `nested![3]` must bind
768                    // `$x` to `3` and `$xs` to nothing, so its expansion bottoms out at
769                    // `nested![]`. Without this a recursive list macro never reaches its base
770                    // case, and the pattern every `macro_rules` list is written with does not work.
771                    None => {
772                        let rest_hole = match (pieces.get(i + 1), pieces.get(i + 2)) {
773                            (Some(Piece::Tok(_)), Some(Piece::Hole(h))) => h.clone(),
774                            _ => return None,
775                        };
776                        let end = capture_end(text, start, None)?;
777                        let captured = text[start..end].trim();
778                        if captured.is_empty() {
779                            return None;
780                        }
781                        bindings.insert(name.clone(), captured.to_string());
782                        bindings.insert(rest_hole, String::new());
783                        pos = end;
784                        i += 2;
785                    }
786                }
787            }
788        }
789        i += 1;
790    }
791    Some((pos, bindings))
792}
793
794/// Where a hole's capture ends: at `stop` when it appears at bracket depth zero, or at the close
795/// bracket that ends the enclosing group when the hole is the pattern's last piece.
796fn capture_end(text: &str, start: usize, stop: Option<&str>) -> Option<usize> {
797    let mut depth = 0i32;
798    let mut i = start;
799    while i < text.len() {
800        if !text.is_char_boundary(i) {
801            i += 1;
802            continue;
803        }
804        let rest = &text[i..];
805        if depth == 0 {
806            match stop {
807                Some(tok) if rest.starts_with(tok) => return Some(i),
808                None if rest.starts_with([')', ']', '}']) => return Some(i),
809                _ => {}
810            }
811        }
812        let c = rest.chars().next().expect("char boundary");
813        match c {
814            '(' | '[' | '{' => depth += 1,
815            ')' | ']' | '}' => {
816                depth -= 1;
817                if depth < 0 {
818                    return if stop.is_none() { Some(i) } else { None };
819                }
820            }
821            _ => {}
822        }
823        i += c.len_utf8();
824    }
825    if stop.is_none() && depth == 0 {
826        Some(text.len())
827    } else {
828        None
829    }
830}
831
832/// Does `text` begin with `tok` as a whole token? A token that ends in a word character needs a
833/// word boundary after it, so `bump` does not match inside `bumps`.
834fn token_matches(text: &str, tok: &str) -> bool {
835    if !text.starts_with(tok) {
836        return false;
837    }
838    match tok.as_bytes().last() {
839        Some(&b) if is_word_byte(b) => !is_word_start(&text[tok.len()..]),
840        _ => true,
841    }
842}
843
844fn is_word_byte(b: u8) -> bool {
845    b.is_ascii_alphanumeric() || b == b'_' || b >= 0x80
846}
847
848fn is_word_start(text: &str) -> bool {
849    text.chars()
850        .next()
851        .is_some_and(|c| c.is_alphanumeric() || c == '_' || c == '\'')
852}
853
854fn leading_space(text: &str) -> usize {
855    text.len() - text.trim_start().len()
856}
857
858// ============================================================================================
859// TEXT-LEVEL IDENTIFIER SURGERY (macro hygiene)
860// ============================================================================================
861
862/// Every identifier that occurs in `text`, ignoring string literals and `$hole` names.
863fn identifiers_in(text: &str) -> HashSet<String> {
864    let mut out = HashSet::new();
865    for_each_identifier(text, |name, _, _| {
866        out.insert(name.to_string());
867    });
868    out
869}
870
871/// The names a template BINDS: `fun x : T =>`, `forall x : T,`, `fix f =>`, `let x :=`.
872fn text_binder_names(text: &str) -> HashSet<String> {
873    let mut out = HashSet::new();
874    let mut pending: Option<&'static str> = None;
875    for_each_identifier(text, |name, _, _| {
876        if pending.take().is_some() {
877            out.insert(name.to_string());
878            return;
879        }
880        pending = match name {
881            "fun" | "forall" | "fix" | "let" => Some("binder"),
882            _ => None,
883        };
884    });
885    out
886}
887
888/// Rewrite every standalone identifier that `map` renames.
889fn rename_identifiers_in_text(text: &str, map: &HashMap<String, String>) -> String {
890    let mut edits: Vec<(usize, usize, String)> = Vec::new();
891    for_each_identifier(text, |name, start, end| {
892        if let Some(to) = map.get(name) {
893            edits.push((start, end, to.clone()));
894        }
895    });
896    apply_edits(text, edits)
897}
898
899/// Replace every `$hole` occurrence with the text it captured.
900fn substitute_holes_in_text(text: &str, bindings: &HashMap<String, String>) -> String {
901    let mut edits: Vec<(usize, usize, String)> = Vec::new();
902    let bytes = text.as_bytes();
903    let mut i = 0usize;
904    let mut in_string = false;
905    while i < bytes.len() {
906        if bytes[i] == b'"' {
907            in_string = !in_string;
908            i += 1;
909            continue;
910        }
911        if !in_string && bytes[i] == b'$' {
912            if let Some((name, len)) = scan_name(&text[i + 1..]) {
913                let key = format!("${name}");
914                if let Some(value) = bindings.get(&key) {
915                    edits.push((i, i + 1 + len, value.clone()));
916                }
917                i += 1 + len;
918                continue;
919            }
920        }
921        i += 1;
922    }
923    apply_edits(text, edits)
924}
925
926/// Visit every standalone identifier in `text`, skipping string literals and `$hole` names.
927fn for_each_identifier(text: &str, mut visit: impl FnMut(&str, usize, usize)) {
928    let bytes = text.as_bytes();
929    let mut i = 0usize;
930    let mut in_string = false;
931    while i < bytes.len() {
932        if !text.is_char_boundary(i) {
933            i += 1;
934            continue;
935        }
936        if bytes[i] == b'"' {
937            in_string = !in_string;
938            i += 1;
939            continue;
940        }
941        if in_string {
942            i += 1;
943            continue;
944        }
945        if bytes[i] == b'$' {
946            // A hole is one atom: its name is not an identifier of the template.
947            match scan_name(&text[i + 1..]) {
948                Some((_, len)) => i += 1 + len,
949                None => i += 1,
950            }
951            continue;
952        }
953        match scan_name(&text[i..]) {
954            Some((name, len)) => {
955                visit(&name, i, i + len);
956                i += len;
957            }
958            None => {
959                i += text[i..].chars().next().map_or(1, char::len_utf8);
960            }
961        }
962    }
963}
964
965/// Apply non-overlapping `(start, end, replacement)` edits, right to left.
966fn apply_edits(text: &str, mut edits: Vec<(usize, usize, String)>) -> String {
967    if edits.is_empty() {
968        return text.to_string();
969    }
970    edits.sort_by_key(|(s, _, _)| *s);
971    let mut out = String::with_capacity(text.len());
972    let mut last = 0usize;
973    for (start, end, replacement) in edits {
974        if start < last {
975            continue;
976        }
977        out.push_str(&text[last..start]);
978        out.push_str(&replacement);
979        last = end;
980    }
981    out.push_str(&text[last..]);
982    out
983}
984
985// ============================================================================================
986// TERM-LEVEL SURGERY (notation hygiene)
987// ============================================================================================
988
989/// Every `Var`/`Global` name and every binder name appearing in a term.
990fn collect_identifiers(term: &Term, out: &mut HashSet<String>) {
991    match term {
992        Term::Var(n) | Term::Global(n) => {
993            out.insert(n.clone());
994        }
995        Term::Const { name, .. } => {
996            out.insert(name.clone());
997        }
998        Term::App(f, a) => {
999            collect_identifiers(f, out);
1000            collect_identifiers(a, out);
1001        }
1002        Term::Lambda {
1003            param,
1004            param_type,
1005            body,
1006        } => {
1007            out.insert(param.clone());
1008            collect_identifiers(param_type, out);
1009            collect_identifiers(body, out);
1010        }
1011        Term::Pi {
1012            param,
1013            param_type,
1014            body_type,
1015        } => {
1016            out.insert(param.clone());
1017            collect_identifiers(param_type, out);
1018            collect_identifiers(body_type, out);
1019        }
1020        Term::Fix { name, body } => {
1021            out.insert(name.clone());
1022            collect_identifiers(body, out);
1023        }
1024        Term::MutualFix { defs, .. } => {
1025            for (n, b) in defs {
1026                out.insert(n.clone());
1027                collect_identifiers(b, out);
1028            }
1029        }
1030        Term::Match {
1031            discriminant,
1032            motive,
1033            cases,
1034        } => {
1035            collect_identifiers(discriminant, out);
1036            collect_identifiers(motive, out);
1037            for c in cases {
1038                collect_identifiers(c, out);
1039            }
1040        }
1041        Term::Let {
1042            name,
1043            ty,
1044            value,
1045            body,
1046        } => {
1047            out.insert(name.clone());
1048            collect_identifiers(ty, out);
1049            collect_identifiers(value, out);
1050            collect_identifiers(body, out);
1051        }
1052        Term::Annotated { term, .. } => collect_identifiers(term, out),
1053        Term::Sort(_) | Term::Lit(_) | Term::Hole => {}
1054    }
1055}
1056
1057/// Rename every binder in `term` to a name that occurs in neither the template nor any argument.
1058fn freshen_binders(
1059    term: &Term,
1060    surface: &Surface,
1061    avoid: &HashSet<String>,
1062    renames: &mut HashMap<String, String>,
1063) -> Term {
1064    let rename = |renames: &mut HashMap<String, String>, p: &String| -> String {
1065        let fresh = surface.fresh_name(p, avoid);
1066        renames.insert(p.clone(), fresh.clone());
1067        fresh
1068    };
1069    match term {
1070        Term::Var(n) => Term::Var(renames.get(n).cloned().unwrap_or_else(|| n.clone())),
1071        Term::Lambda {
1072            param,
1073            param_type,
1074            body,
1075        } => {
1076            let ty = freshen_binders(param_type, surface, avoid, renames);
1077            let fresh = rename(renames, param);
1078            let b = freshen_binders(body, surface, avoid, renames);
1079            Term::Lambda {
1080                param: fresh,
1081                param_type: Box::new(ty),
1082                body: Box::new(b),
1083            }
1084        }
1085        Term::Pi {
1086            param,
1087            param_type,
1088            body_type,
1089        } => {
1090            let ty = freshen_binders(param_type, surface, avoid, renames);
1091            let fresh = if param == "_" {
1092                "_".to_string()
1093            } else {
1094                rename(renames, param)
1095            };
1096            let b = freshen_binders(body_type, surface, avoid, renames);
1097            Term::Pi {
1098                param: fresh,
1099                param_type: Box::new(ty),
1100                body_type: Box::new(b),
1101            }
1102        }
1103        Term::Fix { name, body } => {
1104            let fresh = rename(renames, name);
1105            Term::Fix {
1106                name: fresh,
1107                body: Box::new(freshen_binders(body, surface, avoid, renames)),
1108            }
1109        }
1110        Term::Let {
1111            name,
1112            ty,
1113            value,
1114            body,
1115        } => {
1116            let t = freshen_binders(ty, surface, avoid, renames);
1117            let v = freshen_binders(value, surface, avoid, renames);
1118            let fresh = rename(renames, name);
1119            let b = freshen_binders(body, surface, avoid, renames);
1120            Term::Let {
1121                name: fresh,
1122                ty: Box::new(t),
1123                value: Box::new(v),
1124                body: Box::new(b),
1125            }
1126        }
1127        Term::App(f, a) => Term::App(
1128            Box::new(freshen_binders(f, surface, avoid, renames)),
1129            Box::new(freshen_binders(a, surface, avoid, renames)),
1130        ),
1131        Term::Match {
1132            discriminant,
1133            motive,
1134            cases,
1135        } => Term::Match {
1136            discriminant: Box::new(freshen_binders(discriminant, surface, avoid, renames)),
1137            motive: Box::new(freshen_binders(motive, surface, avoid, renames)),
1138            cases: cases
1139                .iter()
1140                .map(|c| freshen_binders(c, surface, avoid, renames))
1141                .collect(),
1142        },
1143        Term::MutualFix { defs, index } => Term::MutualFix {
1144            defs: defs
1145                .iter()
1146                .map(|(n, b)| {
1147                    let fresh = surface.fresh_name(n, avoid);
1148                    renames.insert(n.clone(), fresh.clone());
1149                    (fresh, freshen_binders(b, surface, avoid, renames))
1150                })
1151                .collect(),
1152            index: *index,
1153        },
1154        Term::Annotated { annotation, term } => Term::Annotated {
1155            annotation: annotation.clone(),
1156            term: Box::new(freshen_binders(term, surface, avoid, renames)),
1157        },
1158        other => other.clone(),
1159    }
1160}
1161
1162/// Replace each `Var(name)` named by `args` with its term. Capture-free because every binder of
1163/// the template has already been renamed away from the hole names.
1164fn substitute_vars(term: &Term, args: &HashMap<String, Term>) -> Term {
1165    match term {
1166        Term::Var(n) => args.get(n).cloned().unwrap_or_else(|| term.clone()),
1167        Term::App(f, a) => Term::App(
1168            Box::new(substitute_vars(f, args)),
1169            Box::new(substitute_vars(a, args)),
1170        ),
1171        Term::Lambda {
1172            param,
1173            param_type,
1174            body,
1175        } => Term::Lambda {
1176            param: param.clone(),
1177            param_type: Box::new(substitute_vars(param_type, args)),
1178            body: Box::new(substitute_vars(body, args)),
1179        },
1180        Term::Pi {
1181            param,
1182            param_type,
1183            body_type,
1184        } => Term::Pi {
1185            param: param.clone(),
1186            param_type: Box::new(substitute_vars(param_type, args)),
1187            body_type: Box::new(substitute_vars(body_type, args)),
1188        },
1189        Term::Fix { name, body } => Term::Fix {
1190            name: name.clone(),
1191            body: Box::new(substitute_vars(body, args)),
1192        },
1193        Term::MutualFix { defs, index } => Term::MutualFix {
1194            defs: defs
1195                .iter()
1196                .map(|(n, b)| (n.clone(), substitute_vars(b, args)))
1197                .collect(),
1198            index: *index,
1199        },
1200        Term::Match {
1201            discriminant,
1202            motive,
1203            cases,
1204        } => Term::Match {
1205            discriminant: Box::new(substitute_vars(discriminant, args)),
1206            motive: Box::new(substitute_vars(motive, args)),
1207            cases: cases.iter().map(|c| substitute_vars(c, args)).collect(),
1208        },
1209        Term::Let {
1210            name,
1211            ty,
1212            value,
1213            body,
1214        } => Term::Let {
1215            name: name.clone(),
1216            ty: Box::new(substitute_vars(ty, args)),
1217            value: Box::new(substitute_vars(value, args)),
1218            body: Box::new(substitute_vars(body, args)),
1219        },
1220        Term::Annotated { annotation, term } => Term::Annotated {
1221            annotation: annotation.clone(),
1222            term: Box::new(substitute_vars(term, args)),
1223        },
1224        other => other.clone(),
1225    }
1226}
1227
1228// ================================================================================================
1229// HIERARCHICAL NAMES AND NAMESPACE RESOLUTION
1230//
1231// Lean's kernel `Environment` maps ABSOLUTE names to constants; `namespace`/`end`/`open` are
1232// elaborator SCOPE state and `resolveGlobalName` runs to completion before anything reaches the
1233// kernel. LOGOS follows that staging exactly: the scope state lives here on the surface, the
1234// resolution runs in the `Repl` (the elaboration driver), and `Context` stays a flat map of
1235// absolute keys. That is what keeps the Milner invariant — the kernel knows no surface — while
1236// still giving a short name a namespace to be relative TO.
1237// ================================================================================================
1238
1239/// The one name that is never relative: `_root_.x` is the absolute `x`, whatever is open.
1240pub const ROOT_ESCAPE: &str = "_root_";
1241
1242/// How an ANONYMOUS section is named in a report. Lean's `section` may be written with no name at
1243/// all, and a scope with no name still has to be reportable — as an unclosed scope at the end of a
1244/// document, and as the scope a mismatched `End` failed to close.
1245pub const ANONYMOUS_SECTION: &str = "an anonymous section";
1246
1247/// How an `End` was WRITTEN, for a report that has to quote it back.
1248fn end_spelling(name: &Option<String>) -> String {
1249    match name {
1250        Some(name) => format!("End {name}"),
1251        None => "End".to_string(),
1252    }
1253}
1254
1255/// A hierarchical name — Lean's `Name`, which is a LIST of components and not a string with dots.
1256///
1257/// The distinction is not cosmetic. To resolve a short name against an enclosing namespace at all
1258/// you must be able to walk the PREFIXES of the current namespace, and to store the answer you must
1259/// be able to re-compose one flat absolute key. A `String` with dots in it can do the second and
1260/// not the first.
1261///
1262/// A component is whatever `scan_name` accepts — ONE grammar for both doors, so every name the
1263/// declaration door takes is a name a term can spell. A `«guillemet name»` is ONE component even
1264/// when it contains a `.`, which is how a name with a dot in it stays writable.
1265#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1266pub struct Name {
1267    parts: Vec<String>,
1268}
1269
1270impl Name {
1271    /// Read a name off the FRONT of `text`, returning it with the bytes consumed. Components are
1272    /// separated by `.` with no surrounding space — `Outer.Inner.value` is three components, and
1273    /// `Outer. Inner` is one component followed by a sentence terminator.
1274    pub fn scan(text: &str) -> Option<(Name, usize)> {
1275        let (first, mut len) = scan_name(text)?;
1276        let mut parts = vec![first];
1277        while text[len..].starts_with('.') {
1278            let after = len + '.'.len_utf8();
1279            match scan_name(&text[after..]) {
1280                Some((part, n)) => {
1281                    parts.push(part);
1282                    len = after + n;
1283                }
1284                // A trailing `.` is the sentence terminator, not an empty component.
1285                None => break,
1286            }
1287        }
1288        Some((Name { parts }, len))
1289    }
1290
1291    /// Read a name that is the WHOLE of `text`.
1292    pub fn parse(text: &str) -> Option<Name> {
1293        match Name::scan(text) {
1294            Some((name, len)) if len == text.len() => Some(name),
1295            _ => None,
1296        }
1297    }
1298
1299    /// A name from components already in hand. `None` for the empty list — there is no such name.
1300    pub fn from_parts(parts: Vec<String>) -> Option<Name> {
1301        (!parts.is_empty()).then_some(Name { parts })
1302    }
1303
1304    /// The flat absolute key the kernel context is indexed by.
1305    pub fn key(&self) -> String {
1306        self.parts.join(".")
1307    }
1308
1309    /// The components, outermost first.
1310    pub fn parts(&self) -> &[String] {
1311        &self.parts
1312    }
1313
1314    /// The final component — the short name, with every namespace stripped.
1315    pub fn short(&self) -> &str {
1316        self.parts
1317            .last()
1318            .expect("a Name always has at least one component")
1319    }
1320
1321    /// Everything but the final component: the namespace a DECLARATION name itself names. For
1322    /// `Foo.Bar.baz` that is `Foo.Bar`, and Lean elaborates such a declaration with `Foo.Bar`
1323    /// current — which is where a short name inside it finds its siblings.
1324    pub fn enclosing(&self) -> Vec<String> {
1325        self.parts[..self.parts.len() - 1].to_vec()
1326    }
1327
1328    /// Does this name's flat key read back as this same name?
1329    ///
1330    /// The kernel `Context` is keyed by a flat `String`, so a name is only HOLDABLE if its key
1331    /// identifies it. `«a.b»` is ONE component and keys at `a.b`, which reads back as the
1332    /// TWO-component `a.b` — two different names, one key, and whichever is declared second
1333    /// answers to the other's spelling with nothing reported. That is this lane's own defect class
1334    /// (a short name quietly denoting a constant nobody wrote) arriving through the guillemet
1335    /// escape instead of through an `open`.
1336    ///
1337    /// A key that reads back as NOTHING AT ALL (`a name with spaces`, `weird?!name`) is holdable:
1338    /// no term-level spelling produces that string either, so there is nothing for it to collide
1339    /// with, and the quoted form stays the escape it was built to be.
1340    pub fn key_reads_back(&self) -> bool {
1341        match Name::parse(&self.key()) {
1342            Some(read_back) => read_back == *self,
1343            None => true,
1344        }
1345    }
1346
1347    /// Can the TERM door spell this name, written exactly as `written`?
1348    ///
1349    /// ★ THE INVARIANT, CHECKED RATHER THAN RESTATED. "Every name the declaration door accepts, a
1350    /// term can spell" is the rule this whole grammar exists for, and `scan_name` gives both doors
1351    /// one COMPONENT grammar — but the term door joins components with its own dot-postfix rule and
1352    /// reserves its own keywords, so two doors that agree about components can still disagree about
1353    /// whole names. `Definition end : Nat := Zero.` and `Definition Outer.«a b» : Nat := Zero.` both
1354    /// scanned clean and both produced a global no `Check` could ever name: write-only names, by a
1355    /// route the component grammar cannot see.
1356    ///
1357    /// So rather than restate the term door's rule here — two grammars again, one step removed —
1358    /// the declaration door hands the spelling to the term door and requires this same name back.
1359    /// The check reads the grammar it is checking against, so it cannot drift from it.
1360    ///
1361    /// The surface is deliberately EMPTY: a name must be spellable with no notation in force, or it
1362    /// is a name whose readability depends on a macro that a later `End` can take away.
1363    pub fn is_spelled_by(&self, written: &str) -> bool {
1364        let Ok(term) = TermParser::parse_in(&Surface::new(), written) else {
1365            return false;
1366        };
1367        match &term {
1368            Term::Global(g) => *g == self.key(),
1369            _ => match flatten_projections(&term) {
1370                Some((Term::Global(head), fields)) => {
1371                    self.parts.len() == fields.len() + 1
1372                        && self.parts[0] == *head
1373                        && self.parts[1..].iter().zip(&fields).all(|(p, f)| p == f)
1374                }
1375                _ => false,
1376            },
1377        }
1378    }
1379
1380    /// Is this name spelled with the `_root_` escape?
1381    pub fn is_root_escaped(&self) -> bool {
1382        self.parts.first().map(String::as_str) == Some(ROOT_ESCAPE)
1383    }
1384
1385    /// The same name with a leading `_root_` removed. `None` if `_root_` was the whole of it.
1386    fn strip_root(&self) -> Option<Name> {
1387        Name::from_parts(self.parts[1..].to_vec())
1388    }
1389
1390    /// The components of `self` that follow `prefix`, or `None` when `prefix` is not one.
1391    ///
1392    /// Lean's `Name.isPrefixOf` (`Lean/Data/Name.lean:46-49`) plus the `replacePrefix` that always
1393    /// follows it: `resolveOpenDecls` (`Lean/ResolveName.lean:146-151`) resolves an explicit
1394    /// `open Foo (x)` for `x.y` by testing that `x` is a PREFIX of `x.y` and swapping it for
1395    /// `Foo.x`. An equal name gives the EMPTY remainder, which is that rule's other half
1396    /// (`openedId == id`, line 144) falling out of the same test.
1397    pub fn after_prefix(&self, prefix: &Name) -> Option<Vec<String>> {
1398        (self.parts.len() >= prefix.parts.len()
1399            && self.parts[..prefix.parts.len()] == prefix.parts[..])
1400            .then(|| self.parts[prefix.parts.len()..].to_vec())
1401    }
1402
1403    /// `self ++ tail`.
1404    fn followed_by(&self, tail: &[String]) -> Name {
1405        let mut parts = self.parts.clone();
1406        parts.extend_from_slice(tail);
1407        Name { parts }
1408    }
1409
1410    /// `prefix ++ self`, as a key.
1411    fn under(&self, prefix: &[String]) -> String {
1412        if prefix.is_empty() {
1413            return self.key();
1414        }
1415        let mut parts: Vec<&str> = prefix.iter().map(String::as_str).collect();
1416        parts.extend(self.parts.iter().map(String::as_str));
1417        parts.join(".")
1418    }
1419}
1420
1421impl std::fmt::Display for Name {
1422    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1423        f.write_str(&self.key())
1424    }
1425}
1426
1427/// What a written name denotes, once the scope has had its say.
1428#[derive(Debug, Clone, PartialEq, Eq)]
1429pub enum Resolution {
1430    /// Exactly one declared constant answers to the name: this absolute key.
1431    Found(String),
1432    /// Nothing declared answers to it. The caller keeps the spelling the user WROTE, so an unknown
1433    /// global is still reported as the unknown global the user asked for and not as some mangled
1434    /// candidate they never typed.
1435    Unresolved(String),
1436    /// Two or more DIFFERENT constants answer to one short name at the same priority. Lean errors
1437    /// here and so do we — a resolver that picks silently changes what a proof is ABOUT, and that
1438    /// is the failure mode with no symptom.
1439    Ambiguous {
1440        written: String,
1441        candidates: Vec<String>,
1442    },
1443}
1444
1445/// Which of Lean's two `OpenDecl` constructors a clause resolves THROUGH, which is the only thing
1446/// that decides whether `protected` can refuse it.
1447///
1448/// `resolveOpenDecls` (`Lean/ResolveName.lean:136-141`) sends a wholesale `open Foo` through
1449/// `resolveQualifiedName`, and that is the one function in Lean that consults `isProtected`
1450/// (line 104). The `explicit` arm (lines 142-154) does not: a name that was WRITTEN in the clause
1451/// was asked for by name, so `protected` has nothing left to protect it from.
1452#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1453enum OpenKind {
1454    /// `Open Foo.` / `Open Foo hiding (a).` — Lean's `OpenDecl.simple`.
1455    Simple,
1456    /// `Open Foo (a).` / `Open Foo renaming a -> b.` — Lean's `OpenDecl.explicit`.
1457    Explicit,
1458}
1459
1460/// One `open` clause: a namespace, and WHICH of its names it brings in, under WHICH spelling.
1461///
1462/// Lean's four forms (`Lean/Elab/Open.lean:63-96`), and they are not sugar for one another:
1463/// `Open Alpha.` brings in all of them; `Open Alpha (a b).` brings in exactly those two;
1464/// `Open Alpha hiding (a).` brings in the rest; `Open Alpha renaming a -> b.` brings in `a` under
1465/// the local spelling `b` and brings in nothing else. Opening a namespace WHOLESALE is what makes
1466/// short names ambiguous in the first place, so the three narrowing forms are the tools for
1467/// AVOIDING an ambiguity rather than being told about one — and `renaming` is the only one of
1468/// them that can bring in a name whose short spelling already means something else here.
1469#[derive(Debug, Clone, PartialEq, Eq)]
1470pub struct OpenClause {
1471    /// The namespace. As written when the clause is parsed; resolved against the current namespace
1472    /// when it is applied.
1473    pub namespace: Name,
1474    /// `Open Alpha (a b).` — ONLY these names are brought in. `None` is the wholesale opening.
1475    pub only: Option<Vec<Name>>,
1476    /// `Open Alpha hiding (a b).` — every name EXCEPT these.
1477    pub hiding: Vec<Name>,
1478    /// `Open Alpha renaming a -> b.` — (the member, the local spelling). A clause with any
1479    /// renaming brings in EXACTLY these, which is why it is its own form and not a modifier on the
1480    /// others: Lean's `renaming` arm emits only `OpenDecl.explicit` entries and no simple open
1481    /// (`Lean/Elab/Open.lean:89-96`), so `open Baz renaming orig → ali` leaves `untouched`
1482    /// unknown.
1483    pub renaming: Vec<(Name, Name)>,
1484}
1485
1486impl OpenClause {
1487    /// `Open Alpha.` — the wholesale opening.
1488    pub fn all(namespace: Name) -> Self {
1489        OpenClause {
1490            namespace,
1491            only: None,
1492            hiding: Vec::new(),
1493            renaming: Vec::new(),
1494        }
1495    }
1496
1497    /// The absolute key this clause offers for `written`, or `None` if it offers none.
1498    ///
1499    /// ★ AN EXPLICIT OPENING MATCHES A PREFIX, A `hiding` MATCHES A WHOLE NAME. That asymmetry is
1500    /// Lean's, and it is measured rather than inferred: `resolveOpenDecls`
1501    /// (`Lean/ResolveName.lean:142-154`) resolves an `OpenDecl.explicit` by `openedId == id` OR
1502    /// `openedId.isPrefixOf id` with the prefix replaced, while the `simple` arm's exclusion list
1503    /// is an exact `exs.contains id` (line 137). So `open Foo (x)` reaches `Foo.x.y` and
1504    /// `open Bar hiding a` still reaches `Bar.a.deep`, both confirmed against lean 4.14.0.
1505    ///
1506    /// Comparing the WHOLE written name for the `only` form — which is what this did — refused
1507    /// `x.y` under `Open Foo (x).`: a false error, on a name Lean resolves.
1508    fn key_for(&self, written: &Name) -> Option<(String, OpenKind)> {
1509        if !self.renaming.is_empty() {
1510            return self.renaming.iter().find_map(|(member, local)| {
1511                let tail = written.after_prefix(local)?;
1512                let key = member.followed_by(&tail).under(&self.namespace.parts);
1513                Some((key, OpenKind::Explicit))
1514            });
1515        }
1516        if self.hiding.iter().any(|h| h == written) {
1517            return None;
1518        }
1519        match &self.only {
1520            Some(only) if !only.iter().any(|o| written.after_prefix(o).is_some()) => None,
1521            Some(_) => Some((written.under(&self.namespace.parts), OpenKind::Explicit)),
1522            None => Some((written.under(&self.namespace.parts), OpenKind::Simple)),
1523        }
1524    }
1525
1526    /// Every name this clause NAMES, which must exist under `namespace` for the clause to mean
1527    /// anything. A clause naming something the namespace does not declare narrows the opening to
1528    /// nothing and reads exactly like one that worked — the silent no-op this surface refuses
1529    /// everywhere else. For a renaming it is the MEMBER that must exist, never the local spelling:
1530    /// the local spelling is the name being minted.
1531    fn named(&self) -> impl Iterator<Item = &Name> {
1532        self.only
1533            .iter()
1534            .flatten()
1535            .chain(self.hiding.iter())
1536            .chain(self.renaming.iter().map(|(member, _)| member))
1537    }
1538}
1539
1540/// One `Namespace …` frame, with the `open` high-water mark it must restore on the way out — an
1541/// `open` written inside a namespace dies with that namespace — and the SECTION depth it was entered
1542/// at, which is the only thing that orders two stacks against each other.
1543#[derive(Debug, Clone)]
1544struct NsFrame {
1545    name: Name,
1546    opens_mark: usize,
1547    /// How many sections were open when this namespace was entered. `End <section>` is refused while
1548    /// this is at least the current section depth, because then no section was opened AFTER the
1549    /// namespace and the namespace is the innermost scope. Without it the two stacks have no
1550    /// relative order and `Section S. Namespace N. End S.` closes the section straight through the
1551    /// namespace, leaving `N` current for every later declaration — and the file still checks.
1552    section_depth: usize,
1553    /// How long the flattened current namespace was before this frame, so leaving is a truncation.
1554    current_len: usize,
1555}
1556
1557/// Where a scope stood, so a scoped `Open … in …` (or a declaration's own implicit namespace) can be
1558/// wound back EXACTLY, without knowing what it pushed.
1559///
1560/// It saves the scope rather than an index into it because `_root_.baz` elaborates at the root: a
1561/// bracketed change may REMOVE frames as well as add them, and a high-water mark cannot restore
1562/// what was cleared. Two short `Vec` clones per command, against a whole elaboration.
1563#[derive(Debug, Clone)]
1564pub struct ScopeMark {
1565    frames: Vec<NsFrame>,
1566    opens: Vec<OpenClause>,
1567    current: Vec<String>,
1568    aliases: Vec<(String, String)>,
1569}
1570
1571/// The namespace scope: what is CURRENT, and what is OPEN.
1572///
1573/// Empty is the state every existing session is in, and in that state [`Namespaces::resolve`] is
1574/// the identity on every name that resolves and on every name that does not — the resolver costs
1575/// one map lookup and changes nothing. That is deliberate: a subsystem that is free when unused is
1576/// one nobody has to opt out of.
1577#[derive(Debug, Clone, Default)]
1578pub struct Namespaces {
1579    /// One frame per `Namespace …` command, outermost first.
1580    frames: Vec<NsFrame>,
1581    /// The flattened current namespace, maintained rather than recomputed. Resolution asks for it
1582    /// once per NAME, and rebuilding a `Vec<String>` each time would put an allocation on the path
1583    /// every identifier in every command takes.
1584    current: Vec<String>,
1585    /// Every opening in force, earliest first, with its namespace already resolved. They sit at ONE
1586    /// priority, which is why two of them answering to one short name is an ambiguity and not a
1587    /// shadow.
1588    opens: Vec<OpenClause>,
1589    /// Every `export`ed name: (the absolute key it answers to here, the absolute key it denotes).
1590    /// A short list consulted on every candidate, and EMPTY unless `Export` was written — which is
1591    /// what keeps it off the path of every session that never exports anything.
1592    aliases: Vec<(String, String)>,
1593    /// Every namespace that EXISTS: entered by a `Namespace` command, or named by the prefix of a
1594    /// name that was declared. Lean errors on `open` of an unknown namespace, and an `open` that
1595    /// brings in nothing at all is the quietest possible way to make a short name mean something
1596    /// other than what was written — so the set is kept, and `open` is checked against it.
1597    known: std::collections::BTreeSet<String>,
1598    /// Every key declared `Protected`.
1599    ///
1600    /// ★ `protected` IS A RESOLUTION FACT, NOT A PERMISSION. Corpus case T02-012 says so in its own
1601    /// guard — "it is inert *logically*, but it changes which short name resolves at a use site".
1602    /// `resolveQualifiedName` (`Lean/ResolveName.lean:104`) drops a protected declaration exactly
1603    /// when the written name is ATOMIC, so `protected def Foo.x` leaves `x` unresolvable both under
1604    /// `open Foo` AND inside `namespace Foo`, while `Foo.x` still works — all three measured
1605    /// against lean 4.14.0. EMPTY unless `Protected` was written, which keeps it off every
1606    /// session's path.
1607    protected_keys: std::collections::BTreeSet<String>,
1608}
1609
1610impl Namespaces {
1611    /// A scope with nothing open and nothing current.
1612    pub fn new() -> Self {
1613        Self::default()
1614    }
1615
1616    /// Is the scope empty? In that state [`resolve`](Self::resolve) is provably the identity — every
1617    /// name that resolves resolves to itself and every name that does not keeps its spelling — which
1618    /// is what makes this subsystem free for every session that never writes `Namespace`, `Open` or
1619    /// `Export`. All three must be absent for the claim to hold, which is why all three are checked.
1620    ///
1621    /// `Protected` is deliberately NOT a fourth: it can only refuse a candidate reached through
1622    /// `lookup_qualified`, which is the enclosing-namespace walk and a
1623    /// wholesale `open` — both of which require a non-empty scope to exist at all. The root reading
1624    /// goes through `lookup` and consults it not at all, exactly as Lean's line 177
1625    /// does not (`ResolveName.lean:177`), so the identity claim survives a `Protected` written at
1626    /// the root.
1627    pub fn is_trivial(&self) -> bool {
1628        self.frames.is_empty() && self.opens.is_empty() && self.aliases.is_empty()
1629    }
1630
1631    /// The components of the current namespace, outermost first.
1632    pub fn current(&self) -> &[String] {
1633        &self.current
1634    }
1635
1636    /// The openings in force, earliest first, each with its namespace already resolved.
1637    pub fn opens(&self) -> &[OpenClause] {
1638        &self.opens
1639    }
1640
1641    /// How many `Namespace \u{2026}` frames are entered.
1642    pub fn depth(&self) -> usize {
1643        self.frames.len()
1644    }
1645
1646    /// The entered namespaces, outermost first \u{2014} what a document must close before it ends.
1647    pub fn frame_names(&self) -> Vec<String> {
1648        self.frames.iter().map(|f| f.name.key()).collect()
1649    }
1650
1651    /// Where the scope stands now.
1652    pub fn mark(&self) -> ScopeMark {
1653        ScopeMark {
1654            frames: self.frames.clone(),
1655            opens: self.opens.clone(),
1656            current: self.current.clone(),
1657            aliases: self.aliases.clone(),
1658        }
1659    }
1660
1661    /// Wind the scope back to `mark`. Used for `Open … in <command>`, which scopes its opens to
1662    /// exactly one command, and for a declaration whose own name names a namespace.
1663    pub fn rewind(&mut self, mark: ScopeMark) {
1664        self.frames = mark.frames;
1665        self.opens = mark.opens;
1666        self.current = mark.current;
1667        self.aliases = mark.aliases;
1668    }
1669
1670    /// Enter the scope a DECLARATION elaborates in, and give back the absolute key it declares.
1671    ///
1672    /// `def Foo.Bar.baz` elaborates with `Foo.Bar` current — a declaration name that NAMES a
1673    /// namespace opens it for its own body, which is where a short sibling name is found. That is
1674    /// Lean's rule and it is the whole of T02-011's third assertion. `_root_.baz` declares the
1675    /// top-level name and elaborates at the root, which is what the escape is for.
1676    pub fn enter_declaration(
1677        &mut self,
1678        declared: &Name,
1679        section_depth: usize,
1680    ) -> (String, ScopeMark) {
1681        let mark = self.mark();
1682        let absolute = match declared.strip_root() {
1683            Some(stripped) if declared.is_root_escaped() => {
1684                // The escape elaborates at the ROOT, so the current namespace goes with the frames.
1685                self.frames.clear();
1686                self.current.clear();
1687                stripped
1688            }
1689            _ => declared.clone(),
1690        };
1691        let key = absolute.under(&self.current);
1692        if let Some(inner) = Name::from_parts(absolute.enclosing()) {
1693            self.push_frame(&inner, section_depth);
1694        }
1695        (key, mark)
1696    }
1697
1698    /// `Namespace Foo.Bar.` — push one frame. `Foo.Bar` is ONE frame of two components, so
1699    /// `End Foo.Bar.` closes it and `End Bar.` does not.
1700    pub fn enter(&mut self, name: &Name, section_depth: usize) {
1701        let mut prefix = self.current.clone();
1702        for part in &name.parts {
1703            prefix.push(part.clone());
1704            self.known.insert(prefix.join("."));
1705        }
1706        self.push_frame(name, section_depth);
1707    }
1708
1709    /// Enter a namespace WITHOUT declaring that it exists. Used for the scope a declaration's own
1710    /// name implies: the namespace comes into being when the declaration lands, not when it is
1711    /// attempted, so a refused `Definition Typo.x := …` leaves nothing to `Open`.
1712    fn push_frame(&mut self, name: &Name, section_depth: usize) {
1713        self.frames.push(NsFrame {
1714            name: name.clone(),
1715            opens_mark: self.opens.len(),
1716            section_depth,
1717            current_len: self.current.len(),
1718        });
1719        self.current.extend(name.parts.iter().cloned());
1720    }
1721
1722    /// Note that the absolute key `key` has been declared, so every namespace its prefixes name now
1723    /// EXISTS and can be opened. Called on SUCCESS only — a refused declaration creates nothing.
1724    pub fn record_declaration(&mut self, key: &str) {
1725        let Some(name) = Name::parse(key) else { return };
1726        for depth in 1..name.parts.len() {
1727            self.known.insert(name.parts[..depth].join("."));
1728        }
1729    }
1730
1731    /// Mark the absolute key `key` as `protected`: it stops answering to its SHORT name, here and
1732    /// under any `open`, and keeps answering to its full one. Called on SUCCESS only, like
1733    /// [`record_declaration`](Self::record_declaration) — a refused declaration protects nothing.
1734    pub fn record_protected(&mut self, key: &str) {
1735        self.protected_keys.insert(key.to_string());
1736    }
1737
1738    /// Is `key` protected against being reached by the ATOMIC name `written`?
1739    ///
1740    /// Both halves are Lean's, in one test: `!id.isAtomic || !isProtected env resolvedId`
1741    /// (`Lean/ResolveName.lean:104`). A qualified reference is never refused by `protected`, which
1742    /// is why `Foo.x` still resolves and `x` does not.
1743    fn shields(&self, key: &str, written: &Name) -> bool {
1744        written.parts.len() == 1 && self.protected_keys.contains(key)
1745    }
1746
1747    /// Which namespace `written` names, read relative to the current one: the longest enclosing
1748    /// prefix that exists, else the bare name if that exists. `None` if no such namespace has ever
1749    /// been declared — which is what makes `Open Typo.` an error rather than a silent no-op.
1750    pub fn resolve_namespace(&self, written: &Name) -> Option<Name> {
1751        if let Some(stripped) = written.strip_root().filter(|_| written.is_root_escaped()) {
1752            return self.known.contains(&stripped.key()).then_some(stripped);
1753        }
1754        let current = &self.current;
1755        for depth in (0..=current.len()).rev() {
1756            let mut parts = current[..depth].to_vec();
1757            parts.extend(written.parts.iter().cloned());
1758            let candidate = Name { parts };
1759            if self.known.contains(&candidate.key()) {
1760                return Some(candidate);
1761            }
1762        }
1763        None
1764    }
1765
1766    /// `End Foo.Bar.` — pop the innermost frame if it is the one named, restoring the `open` state
1767    /// it was entered with. `false` if the innermost open scope is not that namespace, which is how
1768    /// the same `End` keyword serves both namespaces and sections without either guessing.
1769    pub fn leave(&mut self, name: &Name) -> bool {
1770        match self.frames.last() {
1771            Some(frame) if frame.name == *name => {
1772                let (opens_mark, current_len) = (frame.opens_mark, frame.current_len);
1773                self.frames.pop();
1774                self.opens.truncate(opens_mark);
1775                self.current.truncate(current_len);
1776                // ★ AN `export` OUTLIVES ITS NAMESPACE, AN `open` DOES NOT. This truncated the
1777                // alias list here too, on the reasoning that both are spellings written inside a
1778                // scope — but they are not the same kind of thing and Lean does not treat them as
1779                // one. `addAlias` (`Lean/ResolveName.lean:70-71`) is a persistent ENVIRONMENT
1780                // extension, while an `OpenDecl` is elaborator `Scope` state; measured on lean
1781                // 4.14.0, `namespace B export A (x) end B` leaves `#eval B.x` printing 5. Dying
1782                // with the namespace made `export` useless for the one thing it exists for —
1783                // publishing a name from inside the namespace that is to answer to it.
1784                true
1785            }
1786            _ => false,
1787        }
1788    }
1789
1790    /// The namespace that is the INNERMOST open scope, if a namespace is — i.e. if no section has
1791    /// been opened since it was entered. `End` consults this so one keyword can serve both stacks
1792    /// without either of them guessing.
1793    pub fn innermost_scope(&self, section_depth: usize) -> Option<&Name> {
1794        self.frames
1795            .last()
1796            .filter(|f| section_depth <= f.section_depth)
1797            .map(|f| &f.name)
1798    }
1799
1800    /// `Open Foo.` — bring a namespace's short names into scope, reading `Foo` relative to the
1801    /// current namespace. A namespace nothing has ever declared into is REFUSED, as Lean refuses it:
1802    /// an `open` that brings in nothing looks exactly like an `open` that worked, and every short
1803    /// name under it then quietly means the root's constant instead.
1804    pub fn open(&mut self, name: Name) -> Result<(), ParseError> {
1805        self.open_clause(OpenClause::all(name), &|_| true)
1806    }
1807
1808    /// [`open`](Self::open) for a clause that NARROWS the opening — `Open Alpha (a b).` and
1809    /// `Open Alpha hiding (a).`
1810    ///
1811    /// `known` is the environment predicate, and it is needed here for the reason `export` needs
1812    /// one: a clause that names something the namespace does not declare narrows the opening to
1813    /// nothing and is indistinguishable from one that worked. The wholesale form names nothing, so
1814    /// [`open`](Self::open) can still be called without an environment.
1815    pub fn open_clause(
1816        &mut self,
1817        clause: OpenClause,
1818        known: &dyn Fn(&str) -> bool,
1819    ) -> Result<(), ParseError> {
1820        let resolved = self
1821            .resolve_namespace(&clause.namespace)
1822            .ok_or_else(|| ParseError::UnknownNamespace(clause.namespace.key()))?;
1823        for name in clause.named() {
1824            let target = name.under(&resolved.parts);
1825            if self.lookup(&target, known).is_none() {
1826                return Err(ParseError::Missing(format!(
1827                    "`{target}` — an `Open {resolved}` clause names `{name}`, which {resolved} \
1828                     does not declare, so the clause would bring in nothing while reading exactly \
1829                     like one that worked"
1830                )));
1831            }
1832        }
1833        self.opens.push(OpenClause {
1834            namespace: resolved,
1835            ..clause
1836        });
1837        Ok(())
1838    }
1839
1840    /// The absolute key a DECLARATION written here gets: the current namespace, then the name as
1841    /// written. `_root_.x` declares the absolute `x`.
1842    pub fn declaration_key(&self, declared: &Name) -> String {
1843        match declared.strip_root() {
1844            Some(absolute) if declared.is_root_escaped() => absolute.key(),
1845            _ => declared.under(&self.current),
1846        }
1847    }
1848
1849    /// What the absolute key `key` denotes: itself if something is declared there, the `export`ed
1850    /// target if it is an alias, and nothing otherwise. Aliases are consulted FIRST so an `export`
1851    /// into a namespace that also has a real declaration of that name cannot be shadowed by it —
1852    /// `export` is refused in that case, below, so the two can never both exist.
1853    fn lookup(&self, key: &str, known: &dyn Fn(&str) -> bool) -> Option<String> {
1854        if let Some((_, target)) = self.aliases.iter().rev().find(|(alias, _)| alias == key) {
1855            return Some(target.clone());
1856        }
1857        known(key).then(|| key.to_string())
1858    }
1859
1860    /// [`lookup`](Self::lookup) through Lean's `resolveQualifiedName`, which is the ONE path that
1861    /// consults `protected` (`Lean/ResolveName.lean:100-110`). The enclosing-namespace walk and a
1862    /// wholesale `open` both go through it; the exact root reading, the `_root_` escape and an
1863    /// EXPLICIT `open` do not, which is why `Foo.x`, `_root_.Foo.x` and `open Foo (x)` all still
1864    /// reach a protected declaration.
1865    fn lookup_qualified(
1866        &self,
1867        key: &str,
1868        written: &Name,
1869        known: &dyn Fn(&str) -> bool,
1870    ) -> Option<String> {
1871        if let Some((_, target)) = self.aliases.iter().rev().find(|(alias, _)| alias == key) {
1872            return Some(target.clone());
1873        }
1874        (known(key) && !self.shields(key, written)).then(|| key.to_string())
1875    }
1876
1877    /// `Export Alpha (token other).` — make each `Alpha.<name>` answer to `<name>` in the CURRENT
1878    /// namespace, PERMANENTLY — an alias is an environment fact, not scope state, so unlike an
1879    /// `open` it survives the `End` of the namespace it was written in. That is Lean's own
1880    /// staging (`addAlias` is a persistent env extension, `OpenDecl` is elaborator `Scope` state)
1881    /// and it is what `export` is for: publishing a name from inside the namespace that is to
1882    /// answer to it.
1883    ///
1884    /// Every failure mode is LOUD, because a silent one is an alias nobody can see: an unknown source
1885    /// namespace, a name that namespace does not declare, and a name the current namespace already
1886    /// declares (which would leave two readings of one key and no way to say which was meant).
1887    pub fn export(
1888        &mut self,
1889        namespace: &Name,
1890        names: &[Name],
1891        known: &dyn Fn(&str) -> bool,
1892    ) -> Result<(), ParseError> {
1893        let source = self
1894            .resolve_namespace(namespace)
1895            .ok_or_else(|| ParseError::UnknownNamespace(namespace.key()))?;
1896        let mut pending: Vec<(String, String)> = Vec::with_capacity(names.len());
1897        for name in names {
1898            let target = name.under(&source.parts);
1899            if self.lookup(&target, known).is_none() {
1900                return Err(ParseError::Missing(format!(
1901                    "`{target}` \u{2014} `Export {source} ({name})` names something {source} does not \
1902                     declare, so the alias would point nowhere"
1903                )));
1904            }
1905            let alias = name.under(&self.current);
1906            if alias == target {
1907                continue; // exporting a namespace into itself is already true
1908            }
1909            // A real declaration at the alias key, or an earlier export pointing somewhere ELSE,
1910            // both leave one key with two readings. Lean reports that; so do we, rather than let
1911            // the later one win in silence.
1912            let clash = known(&alias)
1913                .then(|| alias.clone())
1914                .or_else(|| {
1915                    self.aliases
1916                        .iter()
1917                        .find(|(a, t)| *a == alias && *t != target)
1918                        .map(|(_, t)| t.clone())
1919                })
1920                .or_else(|| {
1921                    pending
1922                        .iter()
1923                        .find(|(a, t)| *a == alias && *t != target)
1924                        .map(|(_, t)| t.clone())
1925                });
1926            if let Some(other) = clash {
1927                return Err(ParseError::AmbiguousName {
1928                    written: alias,
1929                    candidates: vec![other, target],
1930                });
1931            }
1932            pending.push((alias, target));
1933        }
1934        self.aliases.extend(pending);
1935        Ok(())
1936    }
1937
1938    /// Resolve a written name to the absolute key it denotes, in Lean's order.
1939    ///
1940    /// 1. `_root_.x` is absolute and consults nothing.
1941    /// 2. The enclosing namespace, LONGEST prefix first. An inner namespace SHADOWS an outer one —
1942    ///    Lean's `resolveUsingNamespace` returns at the first prefix that resolves, so this is a
1943    ///    shadow and never an ambiguity.
1944    /// 3. Every `open` namespace AND the root, at ONE priority. Lean combines them
1945    ///    (`resolveOpenDecls` seeded from the root lookup), so a short name that answers to two
1946    ///    different constants is AMBIGUOUS. This is the case the audit called the dangerous one:
1947    ///    with 50 prelude names already in the root, a short name written under an `open` used to
1948    ///    resolve to the PRELUDE's constant and report nothing. It is now reported.
1949    ///
1950    /// `known` is the environment predicate — "is this absolute key declared?". Passing it in
1951    /// rather than a `Context` is what keeps the kernel out of the surface.
1952    pub fn resolve(&self, written: &Name, known: &dyn Fn(&str) -> bool) -> Resolution {
1953        if written.is_root_escaped() {
1954            let Some(absolute) = written.strip_root() else {
1955                return Resolution::Unresolved(written.key());
1956            };
1957            let key = absolute.key();
1958            return match self.lookup(&key, known) {
1959                Some(target) => Resolution::Found(target),
1960                None => Resolution::Unresolved(key),
1961            };
1962        }
1963
1964        let current = &self.current;
1965        for depth in (1..=current.len()).rev() {
1966            let key = written.under(&current[..depth]);
1967            if let Some(target) = self.lookup_qualified(&key, written, known) {
1968                return Resolution::Found(target);
1969            }
1970        }
1971
1972        // ★ LEAN'S `resolveExact`, AND THE REASON IT IS ITS OWN RUNG. `ResolveName.lean:174`
1973        // calls it between the namespace walk and the opens, and a `some` there RETURNS — the
1974        // `open` decls at line 180 are never consulted. `resolveExact` itself
1975        // (`ResolveName.lean:121-131`) answers `none` for an ATOMIC name, so only a short name
1976        // ever reaches the branch where the root and the opens sit at one priority.
1977        //
1978        // Without this rung a dotted name the root declares was reported AMBIGUOUS the moment any
1979        // `open` offered a second reading: `Alpha.Inner.x` + `Inner.x` under `Open Alpha.` refused
1980        // `Inner.x`, where lean 4.14.0 evaluates it to the root's constant without a word. A false
1981        // error is not the safe side of this trade — it refuses programs Lean accepts, and the
1982        // rule it was enforcing ("two readings must be reported") was never Lean's rule for a name
1983        // that is not short.
1984        //
1985        // Aliases are deliberately NOT consulted here: Lean's `resolveExact` tests the environment
1986        // (`containsDeclOrReserved`), and `getAliases` runs in the LATER branch — so an `export`ed
1987        // dotted name still joins the candidate pool and can still be reported ambiguous.
1988        if written.parts.len() > 1 {
1989            let exact = written.key();
1990            if known(&exact) {
1991                return Resolution::Found(exact);
1992            }
1993        }
1994
1995        // The opens are tried most-recent first, so the one candidate case picks the reading a
1996        // reader would expect; two candidates is an error either way, so the order only decides
1997        // how the message reads.
1998        let mut candidates: Vec<String> = Vec::new();
1999        for open in self.opens.iter().rev() {
2000            let Some((key, kind)) = open.key_for(written) else {
2001                continue;
2002            };
2003            let found = match kind {
2004                OpenKind::Simple => self.lookup_qualified(&key, written, known),
2005                OpenKind::Explicit => self.lookup(&key, known),
2006            };
2007            if let Some(target) = found {
2008                if !candidates.contains(&target) {
2009                    candidates.push(target);
2010                }
2011            }
2012        }
2013        let root = written.key();
2014        if let Some(target) = self.lookup(&root, known) {
2015            if !candidates.contains(&target) {
2016                candidates.push(target);
2017            }
2018        }
2019        match candidates.len() {
2020            0 => Resolution::Unresolved(root),
2021            1 => Resolution::Found(candidates.pop().expect("length checked")),
2022            _ => Resolution::Ambiguous {
2023                written: root,
2024                candidates,
2025            },
2026        }
2027    }
2028}
2029
2030/// Is `name` one of the parser's reserved marker heads rather than a name a user wrote? A marker is
2031/// not spellable by [`scan_name`], so this is belt-and-braces — but the belt is cheap and the
2032/// consequence of resolving `⟨proj⟩` is a term nobody can read.
2033fn is_marker(name: &str) -> bool {
2034    name == crate::elaborate::DOT_MARKER || name == crate::elaborate::ANON_CTOR_MARKER
2035}
2036
2037/// `a.b.c` parses as `⟨proj⟩ (⟨proj⟩ a b) c`. Flatten that to the receiver and the field chain in
2038/// source order, or `None` if the term is not a projection chain at all.
2039fn flatten_projections(term: &Term) -> Option<(&Term, Vec<&str>)> {
2040    let mut fields: Vec<&str> = Vec::new();
2041    let mut cursor = term;
2042    while let Term::App(f, arg) = cursor {
2043        match (f.as_ref(), arg.as_ref()) {
2044            (Term::App(head, receiver), Term::Global(field)) if matches!(head.as_ref(), Term::Global(m) if m == crate::elaborate::DOT_MARKER) =>
2045            {
2046                fields.push(field.as_str());
2047                cursor = receiver;
2048            }
2049            _ => break,
2050        }
2051    }
2052    if fields.is_empty() {
2053        return None;
2054    }
2055    fields.reverse();
2056    Some((cursor, fields))
2057}
2058
2059/// Rebuild `⟨proj⟩ base field` for one projection.
2060fn project(base: Term, field: &str) -> Term {
2061    Term::App(
2062        Box::new(Term::App(
2063            Box::new(Term::Global(crate::elaborate::DOT_MARKER.to_string())),
2064            Box::new(base),
2065        )),
2066        Box::new(Term::Global(field.to_string())),
2067    )
2068}
2069
2070/// The environment a written name is resolved against — the two questions resolution asks of it,
2071/// as closures, which is what keeps the kernel out of the surface.
2072pub struct Env<'e> {
2073    /// Is this absolute key declared?
2074    pub known: &'e dyn Fn(&str) -> bool,
2075    /// The fresh identity this absolute written name denotes, when a shadow redirects it
2076    /// (`Context::shadow_of`).
2077    pub shadow: &'e dyn Fn(&str) -> Option<String>,
2078}
2079
2080impl Env<'_> {
2081    /// What `written` denotes: the absolute key the namespace scope resolves it to, then — unless
2082    /// it was written with the `_root_` escape — the identity a shadow redirects that key to.
2083    ///
2084    /// ★ THE ONE RESOLUTION POINT. Every surface that reads a name reads it here: the term and
2085    /// command doors through [`resolve_names_in`], the literate door as it reads each name, the
2086    /// REPL's `Print Axioms`, `Structure` and `Quotient` references. The shadow is applied AFTER
2087    /// the scope has chosen an absolute key, and to that key alone, so a shadow never takes part in
2088    /// the scope's own decisions (which namespace, which `open`, whether two readings are
2089    /// ambiguous), and the escape keeps meaning what it means in Lean: the environment's own
2090    /// declaration, whatever shadows its name.
2091    pub fn resolve(&self, ns: &Namespaces, written: &Name) -> Resolution {
2092        let resolution = ns.resolve(written, self.known);
2093        if written.is_root_escaped() {
2094            return resolution;
2095        }
2096        match resolution {
2097            Resolution::Found(key) => Resolution::Found((self.shadow)(&key).unwrap_or(key)),
2098            Resolution::Unresolved(key) => match (self.shadow)(&key) {
2099                Some(identity) => Resolution::Found(identity),
2100                None => Resolution::Unresolved(key),
2101            },
2102            ambiguous @ Resolution::Ambiguous { .. } => ambiguous,
2103        }
2104    }
2105}
2106
2107/// Resolve every global name written in `term` against the scope, BEFORE any elaboration sees it —
2108/// Lean's staging, and the whole content of "name resolution completes before translation begins".
2109///
2110/// `env` is the environment ([`Env`]); `locals` is the stack of names that are BOUND here and so
2111/// are not global references at all (an inductive's type parameters arrive as `Global`s and become
2112/// `Var`s only later, so they have to be shielded by hand).
2113///
2114/// Returns `Cow::Borrowed` when nothing in the term needed resolving, so an empty scope costs one
2115/// read-only walk and not a rebuild.
2116pub fn resolve_names_in<'t>(
2117    ns: &Namespaces,
2118    env: &Env<'_>,
2119    locals: &mut Vec<String>,
2120    term: &'t Term,
2121) -> Result<std::borrow::Cow<'t, Term>, ParseError> {
2122    Walk {
2123        ns,
2124        env,
2125        direction: Direction::Resolve,
2126    }
2127    .run(locals, term)
2128}
2129
2130/// [`resolve_names_in`] against an environment that is only its declared keys — `known` — and
2131/// shadows nothing.
2132pub fn resolve_names<'t>(
2133    ns: &Namespaces,
2134    known: &dyn Fn(&str) -> bool,
2135    locals: &mut Vec<String>,
2136    term: &'t Term,
2137) -> Result<std::borrow::Cow<'t, Term>, ParseError> {
2138    resolve_names_in(
2139        ns,
2140        &Env {
2141            known,
2142            shadow: &|_| None,
2143        },
2144        locals,
2145        term,
2146    )
2147}
2148
2149/// The inverse of [`resolve_names_in`], for a term already in KEY space: every global is re-spelled
2150/// so that resolving the result in this same scope reaches exactly the key it holds now.
2151///
2152/// A key its own spelling still reaches is kept. A key its spelling no longer reaches — a
2153/// declaration a shadow hides, or one an enclosing namespace or an `open` captures — is written
2154/// with the `_root_` escape, which reaches it and only it. An identity no surface can spell is
2155/// kept as it is: resolution leaves it alone too.
2156///
2157/// A door that must know what a name MEANS while it reads (the literate surface compiles equation
2158/// sets and reflects statements at parse time) resolves as it reads and hands its output to the
2159/// REPL, which resolves again. Re-spelling the output is what makes that second pass the identity
2160/// rather than a second, different reading of `_root_.add`.
2161pub fn respell_names<'t>(
2162    ns: &Namespaces,
2163    env: &Env<'_>,
2164    locals: &mut Vec<String>,
2165    term: &'t Term,
2166) -> Result<std::borrow::Cow<'t, Term>, ParseError> {
2167    Walk {
2168        ns,
2169        env,
2170        direction: Direction::Respell,
2171    }
2172    .run(locals, term)
2173}
2174
2175/// Which way a [`Walk`] maps the references it meets.
2176#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2177enum Direction {
2178    /// A written name to the key it denotes.
2179    Resolve,
2180    /// A key to a spelling that denotes it.
2181    Respell,
2182}
2183
2184/// One walk over a term's references — the structure is shared by both directions, so resolving
2185/// and re-spelling cannot come to disagree about what is bound, what is a field label and what is
2186/// a reference.
2187struct Walk<'a, 'e> {
2188    ns: &'a Namespaces,
2189    env: &'a Env<'e>,
2190    direction: Direction,
2191}
2192
2193/// The names bound around the node being resolved: the caller's stack, plus a count per name, so
2194/// "is this name bound here?" is one lookup rather than a scan of every enclosing binder. A term
2195/// read back from a printed declaration can sit under thousands of binders, and a scan per name
2196/// made resolving it quadratic.
2197struct Locals<'a> {
2198    stack: &'a mut Vec<String>,
2199    counts: std::collections::HashMap<String, usize>,
2200}
2201
2202impl<'a> Locals<'a> {
2203    fn new(stack: &'a mut Vec<String>) -> Self {
2204        let mut counts = std::collections::HashMap::new();
2205        for name in stack.iter() {
2206            *counts.entry(name.clone()).or_insert(0) += 1;
2207        }
2208        Locals { stack, counts }
2209    }
2210
2211    fn contains(&self, name: &str) -> bool {
2212        self.counts.get(name).is_some_and(|c| *c > 0)
2213    }
2214
2215    fn push(&mut self, name: String) {
2216        *self.counts.entry(name.clone()).or_insert(0) += 1;
2217        self.stack.push(name);
2218    }
2219
2220    fn pop(&mut self) {
2221        if let Some(name) = self.stack.pop() {
2222            if let Some(c) = self.counts.get_mut(&name) {
2223                *c -= 1;
2224            }
2225        }
2226    }
2227}
2228
2229impl Walk<'_, '_> {
2230    fn run<'t>(
2231        &self,
2232        locals: &mut Vec<String>,
2233        term: &'t Term,
2234    ) -> Result<std::borrow::Cow<'t, Term>, ParseError> {
2235        let mut bound = Locals::new(locals);
2236        Ok(match self.step(&mut bound, term)? {
2237            Some(rebuilt) => std::borrow::Cow::Owned(rebuilt),
2238            None => std::borrow::Cow::Borrowed(term),
2239        })
2240    }
2241
2242    /// The key or spelling a REFERENCE `name` maps to, or `None` when it maps to itself.
2243    fn reference(&self, name: &str) -> Result<Option<String>, ParseError> {
2244        let Some(written) = Name::parse(name) else {
2245            return Ok(None);
2246        };
2247        match (self.direction, self.env.resolve(self.ns, &written)) {
2248            (Direction::Resolve, Resolution::Found(key)) => Ok((key != name).then_some(key)),
2249            (Direction::Resolve, Resolution::Unresolved(_)) => Ok(None),
2250            (
2251                Direction::Resolve,
2252                Resolution::Ambiguous {
2253                    written,
2254                    candidates,
2255                },
2256            ) => Err(ParseError::AmbiguousName {
2257                written,
2258                candidates,
2259            }),
2260            (Direction::Respell, Resolution::Found(key)) if key == name => Ok(None),
2261            (Direction::Respell, Resolution::Unresolved(_)) => Ok(None),
2262            (Direction::Respell, _) => Ok(Some(format!("{ROOT_ESCAPE}.{name}"))),
2263        }
2264    }
2265
2266    /// The walk itself. `None` means "unchanged", which is what keeps the untouched case
2267    /// allocation-free all the way down the tree.
2268    fn step(&self, locals: &mut Locals<'_>, term: &Term) -> Result<Option<Term>, ParseError> {
2269        if let Some((pattern, body)) = super::raw_match::as_case(term) {
2270            let pattern = super::raw_match::resolve_pattern(pattern, &mut |name| {
2271                self.run(&mut Vec::new(), name).map(|t| t.into_owned())
2272            })?;
2273            let body = self.step(locals, body)?.unwrap_or_else(|| body.clone());
2274            return Ok(Some(super::raw_match::case(pattern, body)));
2275        }
2276        if let Some((receiver, fields)) = flatten_projections(term) {
2277            return match self.direction {
2278                Direction::Resolve => self.resolve_chain(locals, receiver, &fields),
2279                Direction::Respell => self.respell_chain(locals, receiver, &fields),
2280            };
2281        }
2282
2283        match term {
2284            Term::Global(n) => {
2285                if is_marker(n) || locals.contains(n) {
2286                    return Ok(None);
2287                }
2288                Ok(self.reference(n)?.map(Term::Global))
2289            }
2290            // A constant written at explicit levels (`name.{u}`) is a REFERENCE exactly as a bare
2291            // global is, so its name reads in the same scope — `Nat.rec.{u}` inside `Namespace Nat`
2292            // may be written `rec.{u}`. The level arguments are not names and are kept as written.
2293            Term::Const { name, levels } => Ok(self.reference(name)?.map(|key| Term::Const {
2294                name: key,
2295                levels: levels.clone(),
2296            })),
2297            Term::Var(_) | Term::Sort(_) | Term::Lit(_) | Term::Hole => Ok(None),
2298            Term::App(f, a) => {
2299                let rf = self.step(locals, f)?;
2300                let ra = self.step(locals, a)?;
2301                Ok(rebuild2(rf, ra, f, a, |f, a| {
2302                    Term::App(Box::new(f), Box::new(a))
2303                }))
2304            }
2305            Term::Lambda {
2306                param,
2307                param_type,
2308                body,
2309            } => {
2310                let rt = self.step(locals, param_type)?;
2311                locals.push(param.clone());
2312                let rb = self.step(locals, body);
2313                locals.pop();
2314                let rb = rb?;
2315                Ok(rebuild2(rt, rb, param_type, body, |t, b| Term::Lambda {
2316                    param: param.clone(),
2317                    param_type: Box::new(t),
2318                    body: Box::new(b),
2319                }))
2320            }
2321            Term::Pi {
2322                param,
2323                param_type,
2324                body_type,
2325            } => {
2326                let rt = self.step(locals, param_type)?;
2327                locals.push(param.clone());
2328                let rb = self.step(locals, body_type);
2329                locals.pop();
2330                let rb = rb?;
2331                Ok(rebuild2(rt, rb, param_type, body_type, |t, b| Term::Pi {
2332                    param: param.clone(),
2333                    param_type: Box::new(t),
2334                    body_type: Box::new(b),
2335                }))
2336            }
2337            Term::Fix { name, body } => {
2338                locals.push(name.clone());
2339                let rb = self.step(locals, body);
2340                locals.pop();
2341                Ok(rb?.map(|b| Term::Fix {
2342                    name: name.clone(),
2343                    body: Box::new(b),
2344                }))
2345            }
2346            Term::MutualFix { defs, index } => {
2347                for (n, _) in defs {
2348                    locals.push(n.clone());
2349                }
2350                let mut changed = false;
2351                let mut out = Vec::with_capacity(defs.len());
2352                let mut failure = None;
2353                for (n, b) in defs {
2354                    match self.step(locals, b) {
2355                        Ok(Some(nb)) => {
2356                            changed = true;
2357                            out.push((n.clone(), nb));
2358                        }
2359                        Ok(None) => out.push((n.clone(), b.clone())),
2360                        Err(e) => {
2361                            failure = Some(e);
2362                            break;
2363                        }
2364                    }
2365                }
2366                for _ in defs {
2367                    locals.pop();
2368                }
2369                if let Some(e) = failure {
2370                    return Err(e);
2371                }
2372                Ok(changed.then_some(Term::MutualFix {
2373                    defs: out,
2374                    index: *index,
2375                }))
2376            }
2377            Term::Match {
2378                discriminant,
2379                motive,
2380                cases,
2381            } => {
2382                let rd = self.step(locals, discriminant)?;
2383                let rm = self.step(locals, motive)?;
2384                let mut changed = rd.is_some() || rm.is_some();
2385                let mut out = Vec::with_capacity(cases.len());
2386                for c in cases {
2387                    match self.step(locals, c)? {
2388                        Some(nc) => {
2389                            changed = true;
2390                            out.push(nc);
2391                        }
2392                        None => out.push(c.clone()),
2393                    }
2394                }
2395                Ok(changed.then(|| Term::Match {
2396                    discriminant: Box::new(rd.unwrap_or_else(|| (**discriminant).clone())),
2397                    motive: Box::new(rm.unwrap_or_else(|| (**motive).clone())),
2398                    cases: out,
2399                }))
2400            }
2401            Term::Let {
2402                name,
2403                ty,
2404                value,
2405                body,
2406            } => {
2407                let rt = self.step(locals, ty)?;
2408                let rv = self.step(locals, value)?;
2409                locals.push(name.clone());
2410                let rb = self.step(locals, body);
2411                locals.pop();
2412                let rb = rb?;
2413                if rt.is_none() && rv.is_none() && rb.is_none() {
2414                    return Ok(None);
2415                }
2416                Ok(Some(Term::Let {
2417                    name: name.clone(),
2418                    ty: Box::new(rt.unwrap_or_else(|| (**ty).clone())),
2419                    value: Box::new(rv.unwrap_or_else(|| (**value).clone())),
2420                    body: Box::new(rb.unwrap_or_else(|| (**body).clone())),
2421                }))
2422            }
2423            Term::Annotated { annotation, term } => {
2424                Ok(self.step(locals, term)?.map(|t| Term::Annotated {
2425                    annotation: annotation.clone(),
2426                    term: Box::new(t),
2427                }))
2428            }
2429        }
2430    }
2431
2432    /// A projection chain may be a hierarchical NAME rather than a field access, and Lean's rule is
2433    /// the LONGEST prefix that resolves wins: in `a.b.c`, if the constant `a.b` exists then `c` is
2434    /// its field. Trying the name first and the projection second is Lean's own order for
2435    /// generalized field notation.
2436    fn resolve_chain(
2437        &self,
2438        locals: &mut Locals<'_>,
2439        receiver: &Term,
2440        fields: &[&str],
2441    ) -> Result<Option<Term>, ParseError> {
2442        if let Term::Global(head) = receiver {
2443            if !is_marker(head) && !locals.contains(head) {
2444                // The head is ONE component as the term door read it, so a quoted head with a
2445                // space or a `.` in it is not re-readable as a name — `Name::parse` returns None
2446                // for `a b`, and without the fallback `«a b».value` declares and can never be
2447                // referred to. Taking the head verbatim is also the honest reading: it came off
2448                // `scan_name`, which is what a component IS.
2449                if let Some(base) =
2450                    Name::parse(head).or_else(|| Name::from_parts(vec![head.clone()]))
2451                {
2452                    for take in (0..=fields.len()).rev() {
2453                        let mut parts = base.parts.clone();
2454                        parts.extend(fields[..take].iter().map(|f| f.to_string()));
2455                        let candidate = Name { parts };
2456                        match self.env.resolve(self.ns, &candidate) {
2457                            Resolution::Found(key) => {
2458                                if take == 0 && key == *head {
2459                                    return Ok(None); // the whole chain is field access, untouched
2460                                }
2461                                let mut out = Term::Global(key);
2462                                for field in &fields[take..] {
2463                                    out = project(out, field);
2464                                }
2465                                return Ok(Some(out));
2466                            }
2467                            Resolution::Ambiguous {
2468                                written,
2469                                candidates,
2470                            } => {
2471                                return Err(ParseError::AmbiguousName {
2472                                    written,
2473                                    candidates,
2474                                });
2475                            }
2476                            Resolution::Unresolved(_) => {}
2477                        }
2478                    }
2479                }
2480            }
2481        }
2482        // No prefix of the chain is a name: it is field access on a term. Resolve the RECEIVER and
2483        // rebuild with the fields VERBATIM.
2484        //
2485        // ★ A FIELD IS A LABEL, NOT A REFERENCE. Letting the generic walk descend into the chain
2486        // would hand each field name to the resolver, and inside `Namespace Foo` with a `Foo.fst`
2487        // declared, `p.fst` would silently become `p.Foo.fst` — a projection by a field name the
2488        // structure does not have, produced from source that never said it.
2489        let resolved = self.step(locals, receiver)?;
2490        Ok(resolved.map(|base| fields.iter().fold(base, |acc, field| project(acc, field))))
2491    }
2492
2493    /// Re-spell a projection chain whose receiver is already a key: the receiver is re-spelled
2494    /// and the fields are labels, kept verbatim. A receiver `k` whose chain `k.f…` would read back
2495    /// as a declared NAME has no spelling that survives resolution as a projection, and is refused
2496    /// rather than handed on to be read as something else.
2497    fn respell_chain(
2498        &self,
2499        locals: &mut Locals<'_>,
2500        receiver: &Term,
2501        fields: &[&str],
2502    ) -> Result<Option<Term>, ParseError> {
2503        let respelled = self.step(locals, receiver)?;
2504        let base = respelled.as_ref().unwrap_or(receiver);
2505        if let Term::Global(head) = base {
2506            if !is_marker(head) && !locals.contains(head) {
2507                if let Some(name) = Name::parse(head) {
2508                    for take in 1..=fields.len() {
2509                        let mut parts = name.parts.clone();
2510                        parts.extend(fields[..take].iter().map(|f| f.to_string()));
2511                        if let Resolution::Found(key) = self.env.resolve(self.ns, &Name { parts }) {
2512                            return Err(ParseError::Unsupported {
2513                                what: format!(
2514                                    "the projection `.{}` of `{head}`",
2515                                    fields[..take].join(".")
2516                                ),
2517                                why: format!(
2518                                    "written back, it would read as the declared name `{key}` \
2519                                     rather than as a field of `{head}`"
2520                                ),
2521                            });
2522                        }
2523                    }
2524                }
2525            }
2526        }
2527        Ok(respelled.map(|base| fields.iter().fold(base, |acc, field| project(acc, field))))
2528    }
2529}
2530
2531/// Fill every OMITTED DEFAULT ARGUMENT in `term`, at every application spine in it.
2532///
2533/// ★ THIS IS THE SECOND HALF OF THE SAME STAGING [`resolve_names`] IS THE FIRST HALF OF. Lean's
2534/// order is: resolve what the names denote, expand what the spellings abbreviate, and only then
2535/// hand a term to the elaborator — which is why the kernel needs to know nothing about either.
2536/// `withDefault 1` is a SPELLING of `withDefault 1 3`, exactly as `token` inside `open Alpha` is a
2537/// spelling of `Alpha.token`, so it is expanded here rather than being taught to the elaborator as
2538/// a fourth kind of argument insertion.
2539///
2540/// A BARE NAME IS NOT A CALL SITE. `Check withDefault.` reports the function, and
2541/// `Definition g : Int -> Int := withDefault.` is the function it already was — Lean answers both
2542/// the same way, and filling a default into a term nobody applied would change what an unapplied
2543/// constant MEANS. Filling stops at the first parameter with no default, which is what makes the
2544/// trailing-position rule positional: `f 1` against `(a) (m := 3) (b)` becomes `f 1 3`, still
2545/// awaiting `b`.
2546///
2547/// Takes and returns a `Cow` so it composes directly onto [`resolve_names`]'s result: a term that
2548/// omits no default costs one read-only walk, and a session that declared no default at all costs
2549/// not even that (`Surface::fills_defaults`).
2550pub fn fill_defaults<'t>(
2551    surface: &Surface,
2552    term: std::borrow::Cow<'t, Term>,
2553) -> std::borrow::Cow<'t, Term> {
2554    if !surface.fills_defaults() {
2555        return term;
2556    }
2557    match fill_step(surface, &term) {
2558        Some(rebuilt) => std::borrow::Cow::Owned(rebuilt),
2559        None => term,
2560    }
2561}
2562
2563/// [`fill_defaults`]'s worker. `None` means "unchanged", which is what keeps the untouched case
2564/// allocation-free all the way down the tree.
2565fn fill_step(surface: &Surface, term: &Term) -> Option<Term> {
2566    match term {
2567        Term::App(..) => {
2568            // A spine is filled AS A SPINE. `f a b` is `App(App(f, a), b)`, and a default belongs
2569            // to the whole application rather than to its innermost node — walking the `App` arms
2570            // one at a time would offer `f a` its second argument and then apply `b` to the
2571            // result.
2572            let (head, args) = spine(term);
2573            let mut changed = false;
2574            let filled_head = match fill_step(surface, head) {
2575                Some(h) => {
2576                    changed = true;
2577                    h
2578                }
2579                None => head.clone(),
2580            };
2581            let mut filled: Vec<Term> = args
2582                .iter()
2583                .map(|a| match fill_step(surface, a) {
2584                    Some(f) => {
2585                        changed = true;
2586                        f
2587                    }
2588                    None => (*a).clone(),
2589                })
2590                .collect();
2591            if let Term::Global(name) = filled_head.peel_annotations() {
2592                if let Some(binders) = surface.defaults_of(name) {
2593                    changed |= extend_with_defaults(surface, binders, &mut filled);
2594                }
2595            }
2596            changed.then(|| {
2597                filled
2598                    .into_iter()
2599                    .fold(filled_head, |f, a| Term::App(Box::new(f), Box::new(a)))
2600            })
2601        }
2602        // A NAME ON ITS OWN IS NOT AN APPLICATION, and neither is a literal, a sort or a hole.
2603        Term::Global(_)
2604        | Term::Var(_)
2605        | Term::Sort(_)
2606        | Term::Lit(_)
2607        | Term::Hole
2608        | Term::Const { .. } => None,
2609        Term::Lambda {
2610            param,
2611            param_type,
2612            body,
2613        } => rebuild2(
2614            fill_step(surface, param_type),
2615            fill_step(surface, body),
2616            param_type,
2617            body,
2618            |t, b| Term::Lambda {
2619                param: param.clone(),
2620                param_type: Box::new(t),
2621                body: Box::new(b),
2622            },
2623        ),
2624        Term::Pi {
2625            param,
2626            param_type,
2627            body_type,
2628        } => rebuild2(
2629            fill_step(surface, param_type),
2630            fill_step(surface, body_type),
2631            param_type,
2632            body_type,
2633            |t, b| Term::Pi {
2634                param: param.clone(),
2635                param_type: Box::new(t),
2636                body_type: Box::new(b),
2637            },
2638        ),
2639        Term::Fix { name, body } => fill_step(surface, body).map(|b| Term::Fix {
2640            name: name.clone(),
2641            body: Box::new(b),
2642        }),
2643        Term::MutualFix { defs, index } => {
2644            let mut changed = false;
2645            let out: Vec<(String, Term)> = defs
2646                .iter()
2647                .map(|(n, b)| match fill_step(surface, b) {
2648                    Some(nb) => {
2649                        changed = true;
2650                        (n.clone(), nb)
2651                    }
2652                    None => (n.clone(), b.clone()),
2653                })
2654                .collect();
2655            changed.then_some(Term::MutualFix {
2656                defs: out,
2657                index: *index,
2658            })
2659        }
2660        Term::Match {
2661            discriminant,
2662            motive,
2663            cases,
2664        } => {
2665            let rd = fill_step(surface, discriminant);
2666            let rm = fill_step(surface, motive);
2667            let mut changed = rd.is_some() || rm.is_some();
2668            let out: Vec<Term> = cases
2669                .iter()
2670                .map(|c| match fill_step(surface, c) {
2671                    Some(nc) => {
2672                        changed = true;
2673                        nc
2674                    }
2675                    None => c.clone(),
2676                })
2677                .collect();
2678            changed.then(|| Term::Match {
2679                discriminant: Box::new(rd.unwrap_or_else(|| (**discriminant).clone())),
2680                motive: Box::new(rm.unwrap_or_else(|| (**motive).clone())),
2681                cases: out,
2682            })
2683        }
2684        Term::Let {
2685            name,
2686            ty,
2687            value,
2688            body,
2689        } => {
2690            let rt = fill_step(surface, ty);
2691            let rv = fill_step(surface, value);
2692            let rb = fill_step(surface, body);
2693            if rt.is_none() && rv.is_none() && rb.is_none() {
2694                return None;
2695            }
2696            Some(Term::Let {
2697                name: name.clone(),
2698                ty: Box::new(rt.unwrap_or_else(|| (**ty).clone())),
2699                value: Box::new(rv.unwrap_or_else(|| (**value).clone())),
2700                body: Box::new(rb.unwrap_or_else(|| (**body).clone())),
2701            })
2702        }
2703        Term::Annotated { annotation, term } => fill_step(surface, term).map(|t| Term::Annotated {
2704            annotation: annotation.clone(),
2705            term: Box::new(t),
2706        }),
2707    }
2708}
2709
2710/// Append the defaults the WRITTEN arguments stopped short of, returning whether any were.
2711///
2712/// ★ A WRITTEN ARGUMENT ANSWERS TO AN EXPLICIT BINDER, NOT TO A TELESCOPE POSITION. The filling
2713/// runs BEFORE elaboration, so `{A}` and `[C x]` binders have not been inserted yet and the
2714/// caller's `k`-th argument is the `k`-th EXPLICIT parameter — for
2715/// `{A : Type} -> A -> (m : Int := 7) -> Int`, `tagged Zero` has supplied parameter TWO and the
2716/// default it stopped short of is parameter THREE. Counting positions instead of explicit
2717/// positions read `Zero` as the implicit and filled nothing, which is the silent half of this
2718/// seam: the call is then a partial application and the mistake surfaces as an arity error
2719/// somewhere else entirely.
2720///
2721/// Two things stop the filling, and each is a refusal rather than a guess. A parameter with no
2722/// default ends it (that is what makes the rule positional). And a default that mentions a
2723/// parameter whose argument is NOT written — an implicit the elaborator has yet to solve — is not
2724/// filled at all: substituting the one thing we have for it would put a `Term::Hole` in an
2725/// argument position, and a hole matches ANY type.
2726fn extend_with_defaults(
2727    surface: &Surface,
2728    binders: &[BinderSurface],
2729    written: &mut Vec<Term>,
2730) -> bool {
2731    // Align the written arguments against the telescope: each explicit binder consumes one, each
2732    // inserted binder takes a placeholder that is never a legitimate substitution.
2733    let mut aligned: Vec<Term> = Vec::with_capacity(binders.len());
2734    let mut consumed = 0usize;
2735    let mut at = binders.len();
2736    for (i, binder) in binders.iter().enumerate() {
2737        if binder.kind != BinderKind::Explicit {
2738            aligned.push(Term::Hole);
2739            continue;
2740        }
2741        match written.get(consumed) {
2742            Some(arg) => {
2743                aligned.push(arg.clone());
2744                consumed += 1;
2745            }
2746            None => {
2747                at = i;
2748                break;
2749            }
2750        }
2751    }
2752    if consumed < written.len() {
2753        return false; // more arguments than parameters — the kernel reports that, not this
2754    }
2755
2756    let mut changed = false;
2757    while at < binders.len() && binders[at].kind == BinderKind::Explicit {
2758        let Some(value) = TermParser::inline_default(binders, at, &aligned) else {
2759            break;
2760        };
2761        // A placeholder that reached the value means the default named a parameter the caller has
2762        // not supplied and the elaborator has not yet solved.
2763        if has_hole(&value) && !binders[at].default.as_ref().is_some_and(has_hole) {
2764            break;
2765        }
2766        // The default is itself source, so it may omit a default of its own.
2767        let value = fill_step(surface, &value).unwrap_or(value);
2768        aligned.push(value.clone());
2769        written.push(value);
2770        changed = true;
2771        at += 1;
2772    }
2773    changed
2774}
2775
2776/// Does `term` contain a `Term::Hole` anywhere?
2777fn has_hole(term: &Term) -> bool {
2778    match term {
2779        Term::Hole => true,
2780        Term::App(f, a) => has_hole(f) || has_hole(a),
2781        Term::Lambda {
2782            param_type, body, ..
2783        } => has_hole(param_type) || has_hole(body),
2784        Term::Pi {
2785            param_type,
2786            body_type,
2787            ..
2788        } => has_hole(param_type) || has_hole(body_type),
2789        Term::Fix { body, .. } => has_hole(body),
2790        Term::MutualFix { defs, .. } => defs.iter().any(|(_, b)| has_hole(b)),
2791        Term::Match {
2792            discriminant,
2793            motive,
2794            cases,
2795        } => has_hole(discriminant) || has_hole(motive) || cases.iter().any(has_hole),
2796        Term::Let {
2797            ty, value, body, ..
2798        } => has_hole(ty) || has_hole(value) || has_hole(body),
2799        Term::Annotated { term, .. } => has_hole(term),
2800        Term::Global(_) | Term::Var(_) | Term::Sort(_) | Term::Lit(_) | Term::Const { .. } => false,
2801    }
2802}
2803
2804/// An application split into its head and its arguments, outermost-last — `f a b` is
2805/// `(f, [a, b])`. A non-application is its own head with no arguments.
2806fn spine(term: &Term) -> (&Term, Vec<&Term>) {
2807    let mut args = Vec::new();
2808    let mut cur = term;
2809    while let Term::App(f, a) = cur {
2810        args.push(a.as_ref());
2811        cur = f;
2812    }
2813    args.reverse();
2814    (cur, args)
2815}
2816
2817/// Rebuild a two-child node only if a child changed, cloning the untouched side.
2818fn rebuild2(
2819    left: Option<Term>,
2820    right: Option<Term>,
2821    orig_left: &Term,
2822    orig_right: &Term,
2823    build: impl FnOnce(Term, Term) -> Term,
2824) -> Option<Term> {
2825    if left.is_none() && right.is_none() {
2826        return None;
2827    }
2828    Some(build(
2829        left.unwrap_or_else(|| orig_left.clone()),
2830        right.unwrap_or_else(|| orig_right.clone()),
2831    ))
2832}