Skip to main content

logicaffeine_compile/
program_bindings.rs

1//! Program bindings on the tiers that compile a program ahead of time.
2//!
3//! A `## To` function reads and writes the bindings the program's root declares: `Set counter to
4//! counter + 1` inside `## To bump:` moves the `counter` that `Let mutable counter be 0.` made. The
5//! tree-walker keeps them in one table (`Interpreter::globals`) and the VM as program globals. A tier
6//! that compiles `## Main` into a `main` function has nowhere for them but that function's locals,
7//! which no other function can name.
8//!
9//! The rule for what a name means is the tree-walker's (`define_map`, `lookup`, `push_frame`):
10//! - a `Let` at the program's root defines a program binding; a `Let` inside a block of `## Main`
11//!   is that block's, and no function sees it;
12//! - a function's frame holds its parameters and its own `Let`s, block by block, and then the
13//!   program's bindings — never its caller's locals;
14//! - `Set x to v` writes the innermost binding of `x` in scope, else the program's.
15//!
16//! The lift makes the program bindings some function reaches into the fields of ONE record, handed
17//! to every function that reaches them — directly or through a call — as a `mutable` parameter: the
18//! language's own in-out convention. Every later pass then sees a field of a lent record, which a
19//! call may move, and never a local that moves behind its back. A program in which no function
20//! reaches a program binding is returned unchanged, statement for statement.
21
22use std::collections::{HashMap, HashSet};
23
24use crate::analysis::unify::InferType;
25use crate::analysis::{FieldDef, FieldType, TypeDef, TypeRegistry};
26use crate::ast::stmt::{
27    ClosureBody, DomQuery, Expr, MatchArm, Pattern, ReadSource, SelectBranch, Stmt, StringPart,
28    TypeExpr,
29};
30use crate::ast_map::{self, Arenas, AstMap};
31use crate::intern::{Interner, Symbol};
32
33/// The record the lifted program bindings are fields of.
34pub const PROGRAM_RECORD: &str = "__Program";
35
36/// The binding that holds it in `## Main`, and the first parameter of every function that reaches it.
37pub const PROGRAM_BINDING: &str = "__program";
38
39/// A program whose bindings are fields of [`PROGRAM_RECORD`], with that record registered.
40pub struct Lifted<'a> {
41    pub stmts: Vec<Stmt<'a>>,
42    pub registry: TypeRegistry,
43}
44
45/// Lift the program bindings that functions reach into one lent record (see the module docs).
46/// `Ok(None)` when no function reaches one: the program needs nothing. `Err` names what this tier
47/// cannot express, in the author's terms.
48pub fn lift_program_bindings<'a>(
49    stmts: &[Stmt<'a>],
50    registry: &TypeRegistry,
51    interner: &mut Interner,
52    arenas: Arenas<'a>,
53) -> Result<Option<Lifted<'a>>, String> {
54    // The functions this tier compiles (`callgraph::aot_compiled_functions`): nothing that needs a
55    // document, so neither the browser half of a page nor the bindings only it reaches (its listener
56    // tables, its instance state) are anything to lift.
57    let functions: HashSet<Symbol> =
58        crate::analysis::callgraph::aot_compiled_functions(stmts, interner);
59
60    let mut resolver = Resolver::new(&functions);
61    for (at, s) in stmts.iter().enumerate() {
62        if !matches!(s, Stmt::FunctionDef { .. }) {
63            resolver.root_statement(at, s);
64        }
65    }
66    for s in stmts {
67        if let Stmt::FunctionDef {
68            name, params, body, ..
69        } = s
70        {
71            if functions.contains(name) {
72                resolver.function(*name, params.iter().map(|(p, _)| *p), body);
73            }
74        }
75    }
76
77    let lifted: HashSet<Symbol> = resolver.reached.values().flatten().copied().collect();
78    if lifted.is_empty() {
79        return Ok(None);
80    }
81    let needs = resolver.needs();
82    let reach = resolver.reach_through_calls();
83    resolver.refusals(&lifted, &needs, &reach, interner)?;
84
85    let root_types = match crate::analysis::root_binding_types(stmts, interner, registry) {
86        Ok(types) => types,
87        // The program does not check; the compile reports that where it checks the program.
88        Err(_) => return Ok(None),
89    };
90    let mut order: Vec<Symbol> = lifted.iter().copied().collect();
91    order.sort_by(|a, b| interner.resolve(*a).cmp(interner.resolve(*b)));
92    let mut fields = Vec::with_capacity(order.len());
93    for name in &order {
94        let written = root_types.get(name).and_then(|t| field_type(t, interner));
95        let Some(ty) = written else {
96            return Err(format!(
97                "`{}` is one of the program's bindings, and a function reads it, so this tier keeps it \
98                 where every function can reach it — which needs its type. Its `Let` does not decide \
99                 one; write it: `Let {}: <Type> be …`.",
100                interner.resolve(*name),
101                interner.resolve(*name)
102            ));
103        };
104        fields.push(FieldDef {
105            name: *name,
106            ty,
107            is_public: true,
108        });
109    }
110    let record = interner.intern(PROGRAM_RECORD);
111    let program = interner.intern(PROGRAM_BINDING);
112    let mut out_registry = registry.clone();
113    out_registry.register(
114        record,
115        TypeDef::Struct {
116            fields,
117            generics: Vec::new(),
118            is_portable: false,
119            is_shared: false,
120        },
121    );
122
123    let named: &'a TypeExpr<'a> = arenas.types.alloc(TypeExpr::Named(record));
124    let lent: &'a TypeExpr<'a> = arenas.types.alloc(TypeExpr::Mutable { inner: named });
125    let mut lift = Lift {
126        arenas,
127        reads: &resolver.reads,
128        sets: &resolver.sets,
129        lifted: &lifted,
130        needs: &needs,
131        program,
132        lent,
133    };
134    let fresh = arenas.exprs.alloc(Expr::New {
135        type_name: record,
136        type_args: Vec::new(),
137        init_fields: Vec::new(),
138    });
139    let mut out = Vec::with_capacity(stmts.len() + 1);
140    out.push(Stmt::Let {
141        var: program,
142        ty: Some(named),
143        value: fresh,
144        mutable: true,
145    });
146    for s in stmts {
147        match s {
148            Stmt::Let { var, value, .. } if lifted.contains(var) => {
149                let value = lift.map_expr(value);
150                out.push(Stmt::SetField {
151                    object: lift.program_ref(),
152                    field: *var,
153                    value,
154                });
155            }
156            _ => out.push(lift.map_stmt(s)),
157        }
158    }
159    Ok(Some(Lifted {
160        stmts: out,
161        registry: out_registry,
162    }))
163}
164
165/// A checked type as a record field's declared type; `None` for one a field cannot be declared as.
166fn field_type(t: &InferType, interner: &mut Interner) -> Option<FieldType> {
167    let generic =
168        |base: &str, params: Vec<FieldType>, interner: &mut Interner| FieldType::Generic {
169            base: interner.intern(base),
170            params,
171        };
172    Some(match t {
173        InferType::Seq(item) => {
174            let item = field_type(item, interner)?;
175            generic("Seq", vec![item], interner)
176        }
177        InferType::Set(item) => {
178            let item = field_type(item, interner)?;
179            generic("Set", vec![item], interner)
180        }
181        InferType::Option(item) => {
182            let item = field_type(item, interner)?;
183            generic("Option", vec![item], interner)
184        }
185        InferType::Map(k, v) => {
186            let (k, v) = (field_type(k, interner)?, field_type(v, interner)?);
187            generic("Map", vec![k, v], interner)
188        }
189        InferType::UserDefined(name, args) if args.is_empty() => FieldType::Named(*name),
190        InferType::UserDefined(name, args) => {
191            let params = args
192                .iter()
193                .map(|a| field_type(a, interner))
194                .collect::<Option<Vec<_>>>()?;
195            FieldType::Generic {
196                base: *name,
197                params,
198            }
199        }
200        other => FieldType::Primitive(interner.intern(other.primitive_name()?)),
201    })
202}
203
204/// Which name each identifier and each named write in the program means, by the tree-walker's rule.
205struct Resolver<'a, 'f> {
206    functions: &'f HashSet<Symbol>,
207    /// Every name the program's root has defined so far — all of them, once the root is read.
208    roots: HashSet<Symbol>,
209    /// Where each program binding is first defined: the index of its root statement.
210    defined_at: HashMap<Symbol, usize>,
211    /// The frame being read, innermost block last; empty at the program's root.
212    scopes: Vec<HashSet<Symbol>>,
213    /// The function being read; `None` in `## Main`.
214    current: Option<Symbol>,
215    /// The root statement being read.
216    root_at: usize,
217    /// Each identifier that names a program binding, by node.
218    reads: HashSet<*const Expr<'a>>,
219    /// Each `Set` whose target is a program binding, by node.
220    sets: HashSet<*const Stmt<'a>>,
221    /// Each function's program bindings, named in its own body.
222    reached: HashMap<Symbol, HashSet<Symbol>>,
223    /// Each function's calls.
224    calls: HashMap<Symbol, HashSet<Symbol>>,
225    /// The functions each root statement calls (a `Serve` handler included).
226    root_calls: HashMap<usize, HashSet<Symbol>>,
227    /// A function named other than by a call: `(function, how, where)`.
228    as_values: Vec<(Symbol, &'static str, Option<Symbol>)>,
229    /// A program binding written or read by a form that names it rather than holding an
230    /// expression: `(binding, form, where)`.
231    named_slots: Vec<(Symbol, &'static str, Option<Symbol>)>,
232}
233
234impl<'a, 'f> Resolver<'a, 'f> {
235    fn new(functions: &'f HashSet<Symbol>) -> Self {
236        Self {
237            functions,
238            roots: HashSet::new(),
239            defined_at: HashMap::new(),
240            scopes: Vec::new(),
241            current: None,
242            root_at: 0,
243            reads: HashSet::new(),
244            sets: HashSet::new(),
245            reached: HashMap::new(),
246            calls: HashMap::new(),
247            root_calls: HashMap::new(),
248            as_values: Vec::new(),
249            named_slots: Vec::new(),
250        }
251    }
252
253    fn root_statement(&mut self, at: usize, s: &Stmt<'a>) {
254        self.current = None;
255        self.root_at = at;
256        self.stmt(s);
257    }
258
259    fn function(
260        &mut self,
261        name: Symbol,
262        params: impl Iterator<Item = Symbol>,
263        body: &'a [Stmt<'a>],
264    ) {
265        self.current = Some(name);
266        self.scopes = vec![params.collect()];
267        self.stmts(body);
268        self.scopes.clear();
269        self.current = None;
270    }
271
272    fn is_local(&self, name: Symbol) -> bool {
273        self.scopes.iter().any(|scope| scope.contains(&name))
274    }
275
276    /// `name` defined where the reader stands: a local of the innermost block, or at the root a
277    /// program binding.
278    fn define(&mut self, name: Symbol) {
279        match self.scopes.last_mut() {
280            Some(scope) => {
281                scope.insert(name);
282            }
283            None => {
284                self.roots.insert(name);
285                self.defined_at.entry(name).or_insert(self.root_at);
286            }
287        }
288    }
289
290    /// A program binding the current reader names.
291    fn reach(&mut self, name: Symbol) {
292        if let Some(f) = self.current {
293            self.reached.entry(f).or_default().insert(name);
294        }
295    }
296
297    fn names_program_binding(&self, name: Symbol) -> bool {
298        !self.is_local(name) && self.roots.contains(&name)
299    }
300
301    fn read(&mut self, e: &'a Expr<'a>, name: Symbol) {
302        if self.names_program_binding(name) {
303            self.reads.insert(e as *const _);
304            self.reach(name);
305        } else if !self.is_local(name) && self.functions.contains(&name) {
306            self.as_values
307                .push((name, "passed as a value", self.current));
308        }
309    }
310
311    fn call(&mut self, function: Symbol) {
312        if self.is_local(function) || !self.functions.contains(&function) {
313            return;
314        }
315        match self.current {
316            Some(f) => {
317                self.calls.entry(f).or_default().insert(function);
318            }
319            None => {
320                self.root_calls
321                    .entry(self.root_at)
322                    .or_default()
323                    .insert(function);
324            }
325        }
326    }
327
328    /// A form that writes a binding by name and holds no expression for it.
329    fn named_write(&mut self, name: Symbol, form: &'static str) {
330        if self.names_program_binding(name) {
331            self.named_slots.push((name, form, self.current));
332            self.reach(name);
333        }
334    }
335
336    /// A form that defines a binding by name: a local inside a function or a block, a program
337    /// binding at the root — one this tier can hold only if no function reaches it.
338    fn named_define(&mut self, name: Symbol, form: &'static str) {
339        if self.scopes.is_empty() {
340            self.named_slots.push((name, form, None));
341        }
342        self.define(name);
343    }
344
345    fn stmts(&mut self, block: &'a [Stmt<'a>]) {
346        for s in block {
347            self.stmt(s);
348        }
349    }
350
351    fn scoped(&mut self, binds: impl IntoIterator<Item = Symbol>, block: &'a [Stmt<'a>]) {
352        self.scopes.push(binds.into_iter().collect());
353        self.stmts(block);
354        self.scopes.pop();
355    }
356
357    fn arm(&mut self, arm: &MatchArm<'a>) {
358        self.scoped(arm.bindings.iter().map(|(_, b)| *b), arm.body);
359    }
360
361    fn exprs(&mut self, exprs: &[&'a Expr<'a>]) {
362        for e in exprs {
363            self.expr(e);
364        }
365    }
366
367    fn expr(&mut self, e: &'a Expr<'a>) {
368        match e {
369            Expr::Identifier(name) => self.read(e, *name),
370            Expr::Literal(_) | Expr::Escape { .. } | Expr::OptionNone | Expr::View(_) => {}
371            Expr::BinaryOp { left, right, .. } => {
372                self.expr(left);
373                self.expr(right);
374            }
375            Expr::Not { operand } => self.expr(operand),
376            Expr::Call { function, args } => {
377                self.call(*function);
378                self.exprs(args);
379            }
380            Expr::Index { collection, index } => {
381                self.expr(collection);
382                self.expr(index);
383            }
384            Expr::Slice {
385                collection,
386                start,
387                end,
388            } => {
389                self.expr(collection);
390                self.expr(start);
391                self.expr(end);
392            }
393            Expr::Copy { expr } => self.expr(expr),
394            Expr::Give { value } => self.expr(value),
395            Expr::Length { collection } => self.expr(collection),
396            Expr::Contains { collection, value } => {
397                self.expr(collection);
398                self.expr(value);
399            }
400            Expr::Union { left, right } | Expr::Intersection { left, right } => {
401                self.expr(left);
402                self.expr(right);
403            }
404            Expr::ManifestOf { zone } => self.expr(zone),
405            Expr::ChunkAt { index, zone } => {
406                self.expr(index);
407                self.expr(zone);
408            }
409            Expr::List(items) | Expr::Tuple(items) => self.exprs(items),
410            Expr::Range { start, end } => {
411                self.expr(start);
412                self.expr(end);
413            }
414            Expr::FieldAccess { object, .. } => self.expr(object),
415            Expr::New { init_fields, .. } => {
416                for (_, value) in init_fields {
417                    self.expr(value);
418                }
419            }
420            Expr::NewVariant { fields, .. } => {
421                for (_, value) in fields {
422                    self.expr(value);
423                }
424            }
425            Expr::OptionSome { value } => self.expr(value),
426            Expr::WithCapacity { value, capacity } => {
427                self.expr(value);
428                self.expr(capacity);
429            }
430            Expr::Closure { params, body, .. } => {
431                self.scopes.push(params.iter().map(|(p, _)| *p).collect());
432                match body {
433                    ClosureBody::Expression(x) => self.expr(x),
434                    ClosureBody::Block(b) => self.stmts(b),
435                }
436                self.scopes.pop();
437            }
438            Expr::CallExpr { callee, args } => {
439                match callee {
440                    Expr::Identifier(f) if !self.is_local(*f) && self.functions.contains(f) => {
441                        self.call(*f)
442                    }
443                    _ => self.expr(callee),
444                }
445                self.exprs(args);
446            }
447            Expr::InterpolatedString(parts) => {
448                for part in parts {
449                    if let StringPart::Expr { value, .. } = part {
450                        self.expr(value);
451                    }
452                }
453            }
454            Expr::Dom(q) => match q {
455                DomQuery::Text { selector }
456                | DomQuery::Count { selector }
457                | DomQuery::Value { selector }
458                | DomQuery::Visible { selector }
459                | DomQuery::Hidden { selector }
460                | DomQuery::Disabled { selector }
461                | DomQuery::Checked { selector }
462                | DomQuery::Focused { selector } => self.expr(selector),
463                DomQuery::Journal
464                | DomQuery::JournalShows { .. }
465                | DomQuery::Title
466                | DomQuery::Url => {}
467            },
468        }
469    }
470
471    fn stmt(&mut self, s: &Stmt<'a>) {
472        match s {
473            Stmt::Let { var, value, .. } => {
474                self.expr(value);
475                self.define(*var);
476            }
477            Stmt::Set { target, value } => {
478                self.expr(value);
479                if self.names_program_binding(*target) {
480                    self.sets.insert(s as *const _);
481                    self.reach(*target);
482                }
483            }
484            Stmt::Call { function, args } => {
485                self.call(*function);
486                self.exprs(args);
487            }
488            Stmt::If {
489                cond,
490                then_block,
491                else_block,
492            } => {
493                self.expr(cond);
494                self.scoped([], then_block);
495                if let Some(b) = else_block {
496                    self.scoped([], b);
497                }
498            }
499            Stmt::While {
500                cond,
501                body,
502                decreasing,
503            } => {
504                self.expr(cond);
505                if let Some(d) = decreasing {
506                    self.expr(d);
507                }
508                self.scoped([], body);
509            }
510            Stmt::Repeat {
511                pattern,
512                iterable,
513                body,
514            } => {
515                self.expr(iterable);
516                let binds = match pattern {
517                    Pattern::Identifier(x) => vec![*x],
518                    Pattern::Tuple(xs) => xs.clone(),
519                };
520                self.scoped(binds, body);
521            }
522            Stmt::Return { value } => {
523                if let Some(v) = value {
524                    self.expr(v);
525                }
526            }
527            Stmt::RuntimeAssert {
528                condition, reason, ..
529            } => {
530                self.expr(condition);
531                if let Some(r) = reason {
532                    self.expr(r);
533                }
534            }
535            Stmt::Fail { reason } => self.expr(reason),
536            Stmt::Give { object, recipient } => {
537                self.expr(object);
538                self.expr(recipient);
539            }
540            Stmt::Show { object, recipient } => {
541                self.expr(object);
542                match recipient {
543                    Expr::Identifier(f) if !self.is_local(*f) && self.functions.contains(f) => {
544                        self.call(*f)
545                    }
546                    _ => self.expr(recipient),
547                }
548            }
549            Stmt::SetField { object, value, .. } => {
550                self.expr(object);
551                self.expr(value);
552            }
553            Stmt::FunctionDef {
554                name, params, body, ..
555            } => {
556                let (outer_scopes, outer) = (std::mem::take(&mut self.scopes), self.current);
557                self.function(*name, params.iter().map(|(p, _)| *p), body);
558                self.scopes = outer_scopes;
559                self.current = outer;
560            }
561            // `## Test` bodies run on the interpreter tier only; a compiled program never holds one.
562            Stmt::TestDef { .. } => {}
563            Stmt::Expect { condition } => self.expr(condition),
564            Stmt::ExpectFail { body, .. } => self.scoped([], body),
565            Stmt::AdvanceTime { millis } => self.expr(millis),
566            Stmt::Observe { payloads, .. } => {
567                for (_, value) in payloads {
568                    self.expr(value);
569                }
570            }
571            Stmt::During { body, .. } => self.scoped([], body),
572            Stmt::ForAll {
573                vars,
574                constraint,
575                body,
576                ..
577            } => {
578                self.scopes.push(vars.iter().copied().collect());
579                if let Some(c) = constraint {
580                    self.expr(c);
581                }
582                self.stmts(body);
583                self.scopes.pop();
584            }
585            Stmt::Inspect { target, arms, .. } => {
586                self.expr(target);
587                for arm in arms {
588                    self.arm(arm);
589                }
590            }
591            Stmt::Push { value, collection }
592            | Stmt::Add { value, collection }
593            | Stmt::Remove { value, collection } => {
594                self.expr(value);
595                self.expr(collection);
596            }
597            Stmt::Pop { collection, into } => {
598                self.expr(collection);
599                if let Some(x) = into {
600                    self.named_define(*x, "`Pop … into`");
601                }
602            }
603            Stmt::SetIndex {
604                collection,
605                index,
606                value,
607            } => {
608                self.expr(collection);
609                self.expr(index);
610                self.expr(value);
611            }
612            // A splice's statements stand in the block that holds it.
613            Stmt::Splice { body } => self.stmts(body),
614            Stmt::Reversibly { body } => self.scoped([], body),
615            Stmt::Zone { body, .. } => self.scoped([], body),
616            Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => self.scoped([], tasks),
617            Stmt::ReadFrom { var, source } => {
618                if let ReadSource::File(path) = source {
619                    self.expr(path);
620                }
621                self.named_define(*var, "`Read … into`");
622            }
623            Stmt::WriteFile { content, path } => {
624                self.expr(content);
625                self.expr(path);
626            }
627            Stmt::SendMessage {
628                message,
629                destination,
630                ..
631            } => {
632                self.expr(message);
633                self.expr(destination);
634            }
635            Stmt::AwaitMessage { source, into, .. } => {
636                self.expr(source);
637                self.named_define(*into, "`Await … into`");
638            }
639            Stmt::StreamMessage {
640                values,
641                destination,
642            } => {
643                self.expr(values);
644                self.expr(destination);
645            }
646            Stmt::MergeCrdt { source, target } | Stmt::JoinCrdt { source, target } => {
647                self.expr(source);
648                self.expr(target);
649            }
650            Stmt::IncreaseCrdt { object, amount, .. }
651            | Stmt::DecreaseCrdt { object, amount, .. } => {
652                self.expr(object);
653                self.expr(amount);
654            }
655            Stmt::AppendToSequence { sequence, value } => {
656                self.expr(sequence);
657                self.expr(value);
658            }
659            Stmt::ResolveConflict { object, value, .. } => {
660                self.expr(object);
661                self.expr(value);
662            }
663            Stmt::Listen { address, secure } | Stmt::ConnectTo { address, secure } => {
664                self.expr(address);
665                if let Some(pad) = secure {
666                    self.expr(pad.pad);
667                }
668            }
669            Stmt::Serve { port, handler } => {
670                self.expr(port);
671                match handler {
672                    Expr::Identifier(h) if !self.is_local(*h) && self.functions.contains(h) => {
673                        self.call(*h)
674                    }
675                    _ => self.expr(handler),
676                }
677            }
678            Stmt::LetPeerAgent { var, address } => {
679                self.expr(address);
680                self.named_define(*var, "`Let … be a peer agent`");
681            }
682            Stmt::Sleep { milliseconds } => self.expr(milliseconds),
683            Stmt::Sync { var, topic } => {
684                self.expr(topic);
685                self.named_write(*var, "`Sync`");
686            }
687            // A page's request; a compiled program refuses it where it stands.
688            Stmt::Fetch { url, body, .. } => {
689                self.expr(url);
690                if let Some(b) = body {
691                    self.expr(b);
692                }
693            }
694            Stmt::GivenFetch {
695                url, status, body, ..
696            } => {
697                self.expr(url);
698                self.expr(status);
699                self.expr(body);
700            }
701            // The browser surface: lowered to functions before this pass, or refused by the tier.
702            Stmt::DirectiveDef { .. } | Stmt::MiddlewareDef { .. } => {}
703            Stmt::Mount { var, path } => {
704                self.expr(path);
705                self.named_define(*var, "`Mount … at`");
706            }
707            Stmt::LaunchTask { function, args } => {
708                self.call(*function);
709                if self.functions.contains(function) && !self.is_local(*function) {
710                    self.as_values
711                        .push((*function, "launched as a task", self.current));
712                }
713                self.exprs(args);
714            }
715            Stmt::LaunchTaskWithHandle {
716                handle,
717                function,
718                args,
719            } => {
720                self.call(*function);
721                if self.functions.contains(function) && !self.is_local(*function) {
722                    self.as_values
723                        .push((*function, "launched as a task", self.current));
724                }
725                self.exprs(args);
726                self.named_define(*handle, "`Launch a task … with handle`");
727            }
728            Stmt::SendPipe { value, pipe } => {
729                self.expr(value);
730                self.expr(pipe);
731            }
732            Stmt::ReceivePipe { var, pipe } | Stmt::TryReceivePipe { var, pipe } => {
733                self.expr(pipe);
734                self.named_define(*var, "`Receive … from`");
735            }
736            Stmt::TrySendPipe {
737                value,
738                pipe,
739                result,
740            } => {
741                self.expr(value);
742                self.expr(pipe);
743                if let Some(r) = result {
744                    self.named_define(*r, "`Try to send … into`");
745                }
746            }
747            Stmt::StopTask { handle } => self.expr(handle),
748            Stmt::CloseChannel { channel } => self.expr(channel),
749            Stmt::AwaitTask { handle, result } => {
750                self.expr(handle);
751                if let Some(r) = result {
752                    self.named_define(*r, "`Await … into`");
753                }
754            }
755            Stmt::Select { branches } => {
756                for branch in branches {
757                    match branch {
758                        SelectBranch::Receive { var, pipe, body } => {
759                            self.expr(pipe);
760                            self.scoped([*var], body);
761                        }
762                        SelectBranch::Timeout { milliseconds, body } => {
763                            self.expr(milliseconds);
764                            self.scoped([], body);
765                        }
766                    }
767                }
768            }
769            Stmt::Swap { a, b } => {
770                self.named_write(*a, "`Swap`");
771                self.named_write(*b, "`Swap`");
772            }
773            Stmt::CreatePipe { var, .. } => self.named_define(*var, "`Let … be a new Pipe`"),
774            Stmt::Spawn { name, .. } => self.named_define(*name, "`Spawn`"),
775            Stmt::Check { subject, .. } => self.named_write(*subject, "`Check`"),
776            Stmt::Break
777            | Stmt::StructDef { .. }
778            | Stmt::ExpectOutput { .. }
779            | Stmt::GivenFile { .. }
780            | Stmt::Replay { .. }
781            | Stmt::ServeFetches
782            | Stmt::RedirectDef { .. }
783            | Stmt::Escape { .. }
784            | Stmt::Require { .. }
785            | Stmt::Site(_) => {}
786            Stmt::Assert { .. }
787            | Stmt::Trust { .. }
788            | Stmt::Theorem(_)
789            | Stmt::Definition(_)
790            | Stmt::Axiom(_)
791            | Stmt::Theory(_) => {}
792            Stmt::Ui(_) | Stmt::ComponentDef(_) | Stmt::RouteDef { .. } => {}
793        }
794    }
795
796    /// The functions that reach a program binding, directly or through a call.
797    fn needs(&self) -> HashSet<Symbol> {
798        let mut needs: HashSet<Symbol> = self
799            .reached
800            .iter()
801            .filter(|(_, names)| !names.is_empty())
802            .map(|(f, _)| *f)
803            .collect();
804        loop {
805            let more: Vec<Symbol> = self
806                .calls
807                .iter()
808                .filter(|(f, callees)| {
809                    !needs.contains(*f) && callees.iter().any(|c| needs.contains(c))
810                })
811                .map(|(f, _)| *f)
812                .collect();
813            if more.is_empty() {
814                return needs;
815            }
816            needs.extend(more);
817        }
818    }
819
820    /// Every program binding each function reaches, through the functions it calls too.
821    fn reach_through_calls(&self) -> HashMap<Symbol, HashSet<Symbol>> {
822        let mut reach: HashMap<Symbol, HashSet<Symbol>> = self.reached.clone();
823        loop {
824            let mut grew = false;
825            for (f, callees) in &self.calls {
826                let inherited: HashSet<Symbol> = callees
827                    .iter()
828                    .filter_map(|c| reach.get(c))
829                    .flatten()
830                    .copied()
831                    .collect();
832                let own = reach.entry(*f).or_default();
833                let before = own.len();
834                own.extend(inherited);
835                grew |= own.len() != before;
836            }
837            if !grew {
838                return reach;
839            }
840        }
841    }
842
843    /// What this tier cannot hold once the lifted bindings live in a lent record.
844    fn refusals(
845        &self,
846        lifted: &HashSet<Symbol>,
847        needs: &HashSet<Symbol>,
848        reach: &HashMap<Symbol, HashSet<Symbol>>,
849        interner: &Interner,
850    ) -> Result<(), String> {
851        let name = |s: Symbol| interner.resolve(s).to_string();
852        let within = |w: Option<Symbol>| match w {
853            Some(f) => format!("in `{}`", interner.resolve(f)),
854            None => "in `## Main`".to_string(),
855        };
856        let reached_of = |f: Symbol| -> String {
857            let mut names: Vec<String> = reach
858                .get(&f)
859                .into_iter()
860                .flatten()
861                .filter(|x| lifted.contains(x))
862                .map(|x| name(*x))
863                .collect();
864            names.sort();
865            names.join("`, `")
866        };
867        for (binding, form, at) in &self.named_slots {
868            if lifted.contains(binding) {
869                return Err(format!(
870                    "{form} {} writes `{}`, one of the program's bindings that a function reads. On this \
871                     tier such a binding is made with `Let {} be …` and changed with `Set {} to …`.",
872                    within(*at),
873                    name(*binding),
874                    name(*binding),
875                    name(*binding)
876                ));
877            }
878        }
879        for (function, how, at) in &self.as_values {
880            if needs.contains(function) {
881                return Err(format!(
882                    "`{}` is {how} {}, and it reaches the program's `{}`. On this tier a function that \
883                     reaches the program's bindings runs only where it is called.",
884                    name(*function),
885                    within(*at),
886                    reached_of(*function)
887                ));
888            }
889        }
890        let mut calls: Vec<(usize, Symbol)> = self
891            .root_calls
892            .iter()
893            .flat_map(|(at, fs)| fs.iter().map(move |f| (*at, *f)))
894            .collect();
895        calls.sort_by_key(|(at, f)| (*at, name(*f)));
896        for (at, function) in calls {
897            let mut early: Vec<Symbol> = reach
898                .get(&function)
899                .into_iter()
900                .flatten()
901                .filter(|x| lifted.contains(x) && self.defined_at.get(x).is_none_or(|d| *d >= at))
902                .copied()
903                .collect();
904            early.sort_by_key(|x| name(*x));
905            if let Some(binding) = early.first() {
906                return Err(format!(
907                    "`{}` reads the program's `{}`, and `## Main` calls it before `Let {} be …` has run. \
908                     Declare `{}` before the first call.",
909                    name(function),
910                    name(*binding),
911                    name(*binding),
912                    name(*binding)
913                ));
914            }
915        }
916        Ok(())
917    }
918}
919
920/// The rewrite: a lifted binding becomes a field of the lent record, and every function that
921/// reaches one takes the record first.
922struct Lift<'a, 's> {
923    arenas: Arenas<'a>,
924    reads: &'s HashSet<*const Expr<'a>>,
925    sets: &'s HashSet<*const Stmt<'a>>,
926    lifted: &'s HashSet<Symbol>,
927    needs: &'s HashSet<Symbol>,
928    program: Symbol,
929    lent: &'a TypeExpr<'a>,
930}
931
932impl<'a, 's> Lift<'a, 's> {
933    fn program_ref(&self) -> &'a Expr<'a> {
934        self.arenas.exprs.alloc(Expr::Identifier(self.program))
935    }
936
937    fn lent_args(&mut self, args: &[&'a Expr<'a>]) -> Vec<&'a Expr<'a>> {
938        let mut out = Vec::with_capacity(args.len() + 1);
939        out.push(self.program_ref());
940        out.extend(args.iter().map(|a| self.map_expr(a)));
941        out
942    }
943}
944
945impl<'a, 's> AstMap<'a> for Lift<'a, 's> {
946    fn arenas(&self) -> Arenas<'a> {
947        self.arenas
948    }
949
950    fn map_expr(&mut self, e: &'a Expr<'a>) -> &'a Expr<'a> {
951        match e {
952            Expr::Identifier(name)
953                if self.lifted.contains(name) && self.reads.contains(&(e as *const _)) =>
954            {
955                let object = self.program_ref();
956                self.arenas.exprs.alloc(Expr::FieldAccess {
957                    object,
958                    field: *name,
959                })
960            }
961            Expr::Call { function, args } if self.needs.contains(function) => {
962                let args = self.lent_args(args);
963                self.arenas.exprs.alloc(Expr::Call {
964                    function: *function,
965                    args,
966                })
967            }
968            Expr::CallExpr {
969                callee: Expr::Identifier(f),
970                args,
971            } if self.needs.contains(f) => {
972                let args = self.lent_args(args);
973                self.arenas.exprs.alloc(Expr::Call { function: *f, args })
974            }
975            _ => ast_map::walk_expr(self, e),
976        }
977    }
978
979    fn map_stmt(&mut self, s: &Stmt<'a>) -> Stmt<'a> {
980        match s {
981            Stmt::Set { target, value }
982                if self.lifted.contains(target) && self.sets.contains(&(s as *const _)) =>
983            {
984                let value = self.map_expr(value);
985                Stmt::SetField {
986                    object: self.program_ref(),
987                    field: *target,
988                    value,
989                }
990            }
991            Stmt::Call { function, args } if self.needs.contains(function) => Stmt::Call {
992                function: *function,
993                args: self.lent_args(args),
994            },
995            Stmt::Show {
996                object,
997                recipient: Expr::Identifier(f),
998            } if self.needs.contains(f) => Stmt::Call {
999                function: *f,
1000                args: self.lent_args(&[*object]),
1001            },
1002            Stmt::FunctionDef {
1003                name,
1004                generics,
1005                params,
1006                body,
1007                return_type,
1008                is_native,
1009                native_path,
1010                is_exported,
1011                export_target,
1012                opt_flags,
1013            } if self.needs.contains(name) => {
1014                let mut lent_params = Vec::with_capacity(params.len() + 1);
1015                lent_params.push((self.program, self.lent));
1016                lent_params.extend(params.iter().map(|(p, t)| (*p, self.map_type(t))));
1017                Stmt::FunctionDef {
1018                    name: *name,
1019                    generics: generics.clone(),
1020                    params: lent_params,
1021                    body: self.map_block(body),
1022                    return_type: return_type.map(|t| self.map_type(t)),
1023                    is_native: *is_native,
1024                    native_path: *native_path,
1025                    is_exported: *is_exported,
1026                    export_target: *export_target,
1027                    opt_flags: *opt_flags,
1028                }
1029            }
1030            _ => ast_map::walk_stmt(self, s),
1031        }
1032    }
1033}
1034
1035#[cfg(test)]
1036#[path = "../../logicaffeine_tests/unit/logicaffeine_compile/program_bindings/tests.rs"]
1037mod tests;