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(¤t)? {
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(¤t[..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}