Skip to main content

candela/compiler/
compiler.rs

1// This file is derived from keel (https://github.com/horacehoff/keel),
2// Copyright 2026 Horace Hoff, licensed under the Apache License, Version 2.0.
3// It has been modified by the candela authors. See the NOTICE file.
4use crate::cold_path;
5use crate::compiler::compiler_data::InstrSrc;
6use crate::compiler::compiler_data::Source;
7use crate::compiler::compiler_data::TypeNames;
8use crate::compiler::compiler_errors::error_cannot_find_dynlib_symbol;
9use crate::compiler::compiler_errors::error_cannot_load_dynlib;
10use crate::compiler::compiler_errors::error_cannot_push_type_to_array;
11use crate::compiler::compiler_errors::error_cannot_read_file;
12use crate::compiler::compiler_errors::error_conditional_expression_without_else;
13use crate::compiler::compiler_errors::error_division_by_zero;
14use crate::compiler::compiler_errors::error_duplicate_map_key;
15use crate::compiler::compiler_errors::error_invalid_index_type;
16use crate::compiler::compiler_errors::error_invalid_type;
17use crate::compiler::compiler_errors::error_map_diff_types;
18use crate::compiler::compiler_errors::error_non_bool_condition;
19use crate::compiler::compiler_errors::error_not_literal_map_key;
20use crate::compiler::compiler_errors::error_range_invalid_type;
21use crate::compiler::compiler_errors::error_shift_count_out_of_range;
22use crate::compiler::compiler_errors::error_type_arg_count;
23use crate::compiler::compiler_errors::error_type_has_no_c_representation;
24use crate::compiler::compiler_errors::error_type_not_indexable;
25use crate::compiler::compiler_errors::error_unknown_namespace;
26use crate::compiler::imports::ImportResolver;
27use crate::data::NULL;
28use crate::instr::LibFunc;
29use crate::parser;
30use crate::rt::FnValue;
31use crate::rt::LibraryOrigin;
32use crate::rt::TargetOs;
33use crate::rt::resolve_library_filename;
34use crate::vm::CandelaMap;
35use crate::vm::Pool;
36use crate::vm::StringPool;
37use crate::vm::map_insert;
38use crate::vm::shift_count_in_range;
39use crate::{data::Data, instr::Instr};
40use compiler_data::Ctx;
41use compiler_data::DynamicLibFn;
42use compiler_data::Dynamiclib;
43use compiler_data::EnumType;
44use compiler_data::EnumVariant;
45use compiler_data::FnGenerics;
46use compiler_data::FnSignature;
47use compiler_data::Function;
48use compiler_data::HostFnSig;
49use compiler_data::IndirectRegisters;
50use compiler_data::Pools;
51use compiler_data::ReturnCheck;
52use compiler_data::State;
53use compiler_data::Struct;
54use compiler_data::Variable;
55use expr::Expr;
56use expr::ImportBinding;
57use expr::LetAnnotation;
58use expr::METHOD_SEP;
59use expr::Span;
60use expr::StructRest;
61use expr::UNARY_MINUS_METHOD;
62use expr::code_captures_variable;
63use expr::code_modifies_variable;
64use expr::mangle_method;
65use functions::handle_functions;
66use functions::handle_value_call;
67use functions::user_functions::body_being_compiled;
68use functions::user_functions::check_declared_fn;
69use functions::user_functions::declared_holds_fn_signature;
70use functions::user_functions::ensure_indirect_impl;
71use functions::user_functions::handle_user_function;
72use methods::compile_operator_method;
73use methods::handle_method_calls;
74use registers::int_immediate;
75use registers::move_reg_to_reg;
76use registers::move_to_id;
77pub(crate) use registers::use_immediates;
78use rustc_hash::FxHashMap;
79use rustc_hash::FxHashSet;
80use smol_strc::SmolStr;
81use smol_strc::ToSmolStr;
82use std::cell::RefCell;
83use std::hash::BuildHasherDefault;
84use std::hint::unreachable_unchecked;
85use std::path::{Path, PathBuf};
86use std::rc::Rc;
87use type_system::DataType;
88use type_system::FnTypeExpr;
89use type_system::Generics;
90use type_system::ReturnAnnotation;
91use type_system::STRUCT_DEFAULT_METHOD;
92use type_system::TypeCtx;
93use type_system::TypeExpr;
94use type_system::TypeParams;
95use type_system::bare_fn_value_type;
96use type_system::check_if_returns_void;
97use type_system::check_operator_method;
98use type_system::check_struct_default_fn;
99use type_system::collect_direct_fn_calls;
100use type_system::infer_user_fn_return_type;
101use type_system::let_type_fits;
102use type_system::literal_struct_id;
103use type_system::param_type_matches;
104use type_system::pin_inserted_entry;
105use type_system::qualify_duplicate_type_names;
106use type_system::resolve_generic_variant;
107use type_system::struct_field_type_matches;
108use type_system::struct_of_default_fn;
109use type_system::variant_constructor_at;
110
111#[cfg(not(target_arch = "wasm32"))]
112use libloading::Library;
113
114#[cfg(target_arch = "wasm32")]
115use crate::errors::wasm_error;
116
117/// What keeps a bound library open. Where no library loads, a binding is one
118/// of the runtime's intrinsics and there is nothing to hold. A check-only
119/// compile opens no library, so it holds `None`.
120#[cfg(not(target_arch = "wasm32"))]
121type LibHandle = Option<Rc<Library>>;
122#[cfg(target_arch = "wasm32")]
123type LibHandle = ();
124
125thread_local! {
126    /// Set while a check-only compile runs; see [`check_only_scope`].
127    static CHECK_ONLY: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
128}
129
130/// Puts the previous check-only setting back when dropped, so a compile that
131/// unwinds out of an error leaves it as it found it.
132struct CheckOnlyGuard(bool);
133
134impl Drop for CheckOnlyGuard {
135    fn drop(&mut self) {
136        CHECK_ONLY.set(self.0);
137    }
138}
139
140/// Runs `f` with every compile inside it checking only.
141///
142/// A `dylib` block binds the signatures it declares and opens no library,
143/// looks up no symbol and builds no calling interface. The program that comes
144/// out type-checks the same as a full compile but cannot call into a library,
145/// so it is for checking, never for running.
146pub fn check_only_scope<R>(f: impl FnOnce() -> R) -> R {
147    let _guard = CheckOnlyGuard(CHECK_ONLY.replace(true));
148    f()
149}
150
151/// Whether the compile running now is a check-only one.
152#[cfg(not(target_arch = "wasm32"))]
153fn is_check_only() -> bool {
154    CHECK_ONLY.get()
155}
156
157/// A `dylib` block waiting for the types it names to resolve: the file it is
158/// in, its slot in the library table, its signatures, the open library, the
159/// spec an artifact records for it with the search that resolves that spec,
160/// and where it was written.
161type PendingDylib = (
162    u16,
163    u16,
164    Box<[(SmolStr, Box<[TypeExpr]>, TypeExpr, Span)]>,
165    LibHandle,
166    (SmolStr, LibraryOrigin),
167    Span,
168);
169pub mod compiler_data;
170mod compiler_errors;
171pub mod imports;
172mod std_index;
173pub mod type_system;
174
175pub mod expr;
176
177#[path = "functions/functions.rs"]
178mod functions;
179#[path = "functions/methods.rs"]
180mod methods;
181
182mod copies;
183pub(crate) mod flow;
184mod fold;
185pub(crate) mod function_table;
186mod registers;
187mod scalar;
188pub(crate) mod type_test;
189
190pub trait UnwrapId {
191    fn unwrap_id(self) -> u16;
192}
193
194impl UnwrapId for Option<u16> {
195    #[inline(always)]
196    fn unwrap_id(self) -> u16 {
197        debug_assert!(self.is_some());
198        unsafe { self.unwrap_unchecked() }
199    }
200}
201
202/// Fuses the last comparison instruction into a jump instruction (jumps when condition is false)
203fn add_cmp_false(condition_id: u16, len: &mut u16, output: &mut Vec<Instr>, jmp_backwards: bool) {
204    if output.is_empty() {
205        return output.push(Instr::IsFalseJmp(condition_id, *len));
206    }
207    *output.last_mut().unwrap() = match *output.last().unwrap() {
208        Instr::InfFloat(o1, o2, o3) if o3 == condition_id => Instr::SupEqFloatJmp(o1, o2, *len),
209        Instr::InfInt(o1, o2, o3) if o3 == condition_id => Instr::SupEqIntJmp(o1, o2, *len),
210        Instr::InfEqFloat(o1, o2, o3) if o3 == condition_id => Instr::SupFloatJmp(o1, o2, *len),
211        Instr::InfEqInt(o1, o2, o3) if o3 == condition_id => Instr::SupIntJmp(o1, o2, *len),
212        Instr::SupFloat(o1, o2, o3) if o3 == condition_id => Instr::InfEqFloatJmp(o1, o2, *len),
213        Instr::SupInt(o1, o2, o3) if o3 == condition_id => Instr::InfEqIntJmp(o1, o2, *len),
214        Instr::SupEqFloat(o1, o2, o3) if o3 == condition_id => Instr::InfFloatJmp(o1, o2, *len),
215        Instr::SupEqInt(o1, o2, o3) if o3 == condition_id => Instr::InfIntJmp(o1, o2, *len),
216        Instr::Eq(o1, o2, o3) if o3 == condition_id => Instr::NotEqJmp(o1, o2, *len),
217        Instr::ObjEq(o1, o2, o3) if o3 == condition_id => Instr::ObjNotEqJmp(o1, o2, *len),
218        Instr::StrEq(o1, o2, o3) if o3 == condition_id => Instr::StrNotEqJmp(o1, o2, *len),
219        Instr::NotEq(o1, o2, o3) if o3 == condition_id => Instr::EqJmp(o1, o2, *len),
220        Instr::ObjNotEq(o1, o2, o3) if o3 == condition_id => Instr::ObjEqJmp(o1, o2, *len),
221        Instr::StrNotEq(o1, o2, o3) if o3 == condition_id => Instr::StrEqJmp(o1, o2, *len),
222        _ => {
223            output.push(Instr::IsFalseJmp(condition_id, *len));
224            return;
225        }
226    };
227    if jmp_backwards {
228        *len -= 1;
229    }
230}
231
232/// Fuses the last comparison instruction into a jump instruction (jumps when condition is true)
233#[inline(always)]
234fn add_cmp_true(condition_id: u16, output: &mut Vec<Instr>) {
235    if output.is_empty() {
236        return output.push(Instr::IsTrueJmp(condition_id, 0));
237    }
238    let new_instr = match *output.last().unwrap() {
239        Instr::InfFloat(o1, o2, o3) if o3 == condition_id => Instr::InfFloatJmp(o1, o2, 0),
240        Instr::InfInt(o1, o2, o3) if o3 == condition_id => Instr::InfIntJmp(o1, o2, 0),
241        Instr::InfEqFloat(o1, o2, o3) if o3 == condition_id => Instr::InfEqFloatJmp(o1, o2, 0),
242        Instr::InfEqInt(o1, o2, o3) if o3 == condition_id => Instr::InfEqIntJmp(o1, o2, 0),
243        Instr::SupFloat(o1, o2, o3) if o3 == condition_id => Instr::SupFloatJmp(o1, o2, 0),
244        Instr::SupInt(o1, o2, o3) if o3 == condition_id => Instr::SupIntJmp(o1, o2, 0),
245        Instr::SupEqFloat(o1, o2, o3) if o3 == condition_id => Instr::SupEqFloatJmp(o1, o2, 0),
246        Instr::SupEqInt(o1, o2, o3) if o3 == condition_id => Instr::SupEqIntJmp(o1, o2, 0),
247        Instr::Eq(o1, o2, o3) if o3 == condition_id => Instr::EqJmp(o1, o2, 0),
248        Instr::ObjEq(o1, o2, o3) if o3 == condition_id => Instr::ObjEqJmp(o1, o2, 0),
249        Instr::StrEq(o1, o2, o3) if o3 == condition_id => Instr::StrEqJmp(o1, o2, 0),
250        Instr::NotEq(o1, o2, o3) if o3 == condition_id => Instr::NotEqJmp(o1, o2, 0),
251        Instr::ObjNotEq(o1, o2, o3) if o3 == condition_id => Instr::ObjNotEqJmp(o1, o2, 0),
252        Instr::StrNotEq(o1, o2, o3) if o3 == condition_id => Instr::StrNotEqJmp(o1, o2, 0),
253        _ => {
254            output.push(Instr::IsTrueJmp(condition_id, 0));
255            return;
256        }
257    };
258    *output.last_mut().unwrap() = new_instr;
259}
260
261/// Sets the jump size field of a jump instruction
262#[inline(always)]
263const fn set_jmp_size(instr: &mut Instr, size: u16) {
264    match instr {
265        Instr::IsFalseJmp(_, jump_size)
266        | Instr::IsTrueJmp(_, jump_size)
267        | Instr::Jmp(jump_size)
268        | Instr::SupEqFloatJmp(_, _, jump_size)
269        | Instr::SupEqIntJmp(_, _, jump_size)
270        | Instr::SupFloatJmp(_, _, jump_size)
271        | Instr::SupIntJmp(_, _, jump_size)
272        | Instr::InfEqFloatJmp(_, _, jump_size)
273        | Instr::InfEqIntJmp(_, _, jump_size)
274        | Instr::InfFloatJmp(_, _, jump_size)
275        | Instr::InfIntJmp(_, _, jump_size)
276        | Instr::InfIntJmpBack(_, _, jump_size)
277        | Instr::StepIntJmpBack(_, _, jump_size)
278        | Instr::NotEqJmp(_, _, jump_size)
279        | Instr::EqJmp(_, _, jump_size)
280        | Instr::ObjNotEqJmp(_, _, jump_size)
281        | Instr::ObjEqJmp(_, _, jump_size)
282        | Instr::StrNotEqJmp(_, _, jump_size)
283        | Instr::StrEqJmp(_, _, jump_size) => *jump_size = size,
284        _ => unsafe { unreachable_unchecked() },
285    }
286}
287
288/// Compiles one condition operand and checks that it is a `bool`.
289///
290/// candela has no truthiness, so a condition whose type the compiler knows and
291/// which is not `bool` is reported where it is written. A condition typed `any`
292/// carries no type to check against, so it goes through `AsBoolVal`, which
293/// hands a bool straight back and raises `bad_downcast` on anything else; only
294/// that path pays for the check.
295fn compile_condition_operand(
296    expr: &Expr,
297    condition_span: Span,
298    v: &mut Vec<Variable>,
299    ctx: Ctx,
300    state: &mut State<'_>,
301    output: &mut Vec<Instr>,
302) -> u16 {
303    let condition_type = expr.infer_type(v, ctx, state);
304    match condition_type {
305        DataType::Bool => expr
306            .compile(v, ctx, state, output, None, false, true)
307            .unwrap_id(),
308        DataType::Unknown => {
309            let cond_id = expr
310                .compile(v, ctx, state, output, None, false, true)
311                .unwrap_id();
312            state.free_reg(cond_id, v);
313            let checked_id = state.alloc_reg();
314            output.push(Instr::CallLibFunc(LibFunc::AsBoolVal, cond_id, checked_id));
315            checked_id
316        }
317        other => error_non_bool_condition(
318            condition_span,
319            &other,
320            ctx.file_idx,
321            state.sources,
322            state.type_names(),
323        ),
324    }
325}
326
327/// Compiles short-circuit && and || conditions
328/// bool_or_mode true indicates left side of ||, emits true jumps
329/// bool_or_mode false emits false jumps
330/// Returns (true_jump_idxs, false_jump_idxs)
331#[allow(clippy::too_many_arguments)]
332fn compile_short_circuit_condition(
333    expr: &Expr,
334    condition_span: Span,
335    v: &mut Vec<Variable>,
336    ctx: Ctx,
337    state: &mut State<'_>,
338    output: &mut Vec<Instr>,
339    bool_or_mode: bool,
340) -> (Vec<usize>, Vec<usize>) {
341    // What an `is` proves holds only where the code it guards runs. Under an
342    // `||` that is nowhere past its own operand, so the names it brought into
343    // scope leave with it; under a chain of `&&` it is the rest of the chain
344    // and the body, and the caller takes them out of scope after the body.
345    let v_len = v.len();
346    match expr {
347        Expr::BoolOr(left, right, left_span, right_span) => {
348            // left side of || always uses true jump mode
349            let (mut true_jumps, left_false) =
350                compile_short_circuit_condition(left, *left_span, v, ctx, state, output, true);
351            v.truncate(v_len);
352            // A false left operand does not settle `||`, so it continues into
353            // the right operand rather than out of the whole expression.
354            let right_start = output.len();
355            for j in left_false {
356                set_jmp_size(&mut output[j], (right_start - j) as u16);
357            }
358            let (right_true, right_false) = compile_short_circuit_condition(
359                right,
360                *right_span,
361                v,
362                ctx,
363                state,
364                output,
365                bool_or_mode,
366            );
367            true_jumps.extend(right_true);
368            v.truncate(v_len);
369            (true_jumps, right_false)
370        }
371        Expr::BoolAnd(left, right, left_span, right_span) => {
372            if bool_or_mode {
373                // `&&` on the left of `||`, where the caller wants jumps taken
374                // when this conjunction is true. A false left operand settles
375                // the conjunction, so its false jumps skip the right operand
376                // and land on whatever is emitted next, which is exactly where
377                // the enclosing `||` continues.
378                let (_, left_false) =
379                    compile_short_circuit_condition(left, *left_span, v, ctx, state, output, false);
380                let (right_true, _) = compile_short_circuit_condition(
381                    right,
382                    *right_span,
383                    v,
384                    ctx,
385                    state,
386                    output,
387                    true,
388                );
389                let fallthrough = output.len();
390                for j in left_false {
391                    set_jmp_size(&mut output[j], (fallthrough - j) as u16);
392                }
393                v.truncate(v_len);
394                (right_true, Vec::new())
395            } else {
396                // normal && -> if either side is false, jump past the body
397                let (left_true, mut false_jumps) =
398                    compile_short_circuit_condition(left, *left_span, v, ctx, state, output, false);
399                // A true left operand does not settle `&&`, so it continues
400                // into the right operand. Only the right operand's true jumps
401                // settle the conjunction, and the caller aims those at the body.
402                let right_start = output.len();
403                for j in left_true {
404                    set_jmp_size(&mut output[j], (right_start - j) as u16);
405                }
406                let (right_true, right_false) = compile_short_circuit_condition(
407                    right,
408                    *right_span,
409                    v,
410                    ctx,
411                    state,
412                    output,
413                    false,
414                );
415                false_jumps.extend(right_false);
416                (right_true, false_jumps)
417            }
418        }
419        // An `is` whose false jump leaves the code it guards: what it proves
420        // holds past the jump, so a variant's payload is read there.
421        Expr::Is(operand, target, operand_span, target_span) if !bool_or_mode => {
422            let (cond_id, facts) = type_test::compile_is(
423                operand,
424                target,
425                *operand_span,
426                *target_span,
427                None,
428                true,
429                v,
430                ctx,
431                state,
432                output,
433            );
434            add_cmp_false(cond_id, &mut 0, output, false);
435            state.free_reg(cond_id, v);
436            let false_jump = output.len() - 1;
437            facts.apply(v, state, output);
438            (Vec::new(), vec![false_jump])
439        }
440        expr => {
441            let cond_id = compile_condition_operand(expr, condition_span, v, ctx, state, output);
442            if bool_or_mode {
443                add_cmp_true(cond_id, output);
444                state.free_reg(cond_id, v);
445                (vec![output.len() - 1], Vec::new())
446            } else {
447                add_cmp_false(cond_id, &mut 0, output, false);
448                state.free_reg(cond_id, v);
449                (Vec::new(), vec![output.len() - 1])
450            }
451        }
452    }
453}
454
455fn parse_loop_flow_control(
456    loop_code: &mut [Instr],
457    loop_id: u16,
458    code_length: u16,
459    for_loop: bool,
460    indefinite: bool,
461) {
462    loop_code.iter_mut().enumerate().for_each(|(i, x)| {
463        if let Instr::NotEqJmp(break_id, 0, 0) = x
464            && *break_id == loop_id
465        {
466            if for_loop && !indefinite {
467                *x = Instr::Jmp(code_length - i as u16 - 1);
468            } else {
469                *x = Instr::Jmp(code_length - i as u16);
470            }
471        } else if let Instr::EqJmp(continue_id, 0, 0) = x
472            && *continue_id == loop_id
473        {
474            // A `continue` lands on the instruction that ends the turn: a
475            // `for` loop's step, or a `loop` block's or `while` loop's jump
476            // back, each the last instruction of its loop.
477            if for_loop {
478                *x = Instr::Jmp(code_length - i as u16 - 2);
479            } else {
480                *x = Instr::Jmp(code_length - i as u16 - 1);
481            }
482        }
483    });
484}
485
486/// Compiles `expr` as a function value: a list whose first slot says where the
487/// function's body starts, followed by the cells it captured.
488///
489/// A closure that captures already compiles to exactly that, and so does a
490/// value read out of a position that holds one, so a value is built here only
491/// for a function that captures nothing.
492pub(crate) fn compile_fn_value(
493    expr: &Expr,
494    v: &mut Vec<Variable>,
495    ctx: Ctx,
496    state: &mut State<'_>,
497    output: &mut Vec<Instr>,
498    tgt_id: Option<u16>,
499) -> u16 {
500    if let DataType::Fn(fn_id) = expr.infer_type(v, ctx, state)
501        && state.fns[fn_id as usize].captures.is_empty()
502    {
503        return compile_fn_entry_value(fn_id as usize, v, ctx, state, output, tgt_id);
504    }
505    expr.compile(v, ctx, state, output, tgt_id, false, true)
506        .unwrap_id()
507}
508
509/// Builds the value of a function that captures nothing: one slot saying where
510/// the body starts, which is what a call through the value jumps to.
511///
512/// A function that annotates every parameter is compiled at those types here,
513/// unless a call through a value already gave it a body. The value can go where
514/// the compiler stops following it, into an `any`, and come back out as a
515/// `fn(...)` type, so no call site would compile it; the annotations say what
516/// such a call passes.
517pub(crate) fn compile_fn_entry_value(
518    fn_id: usize,
519    v: &mut Vec<Variable>,
520    ctx: Ctx,
521    state: &mut State<'_>,
522    output: &mut Vec<Instr>,
523    tgt_id: Option<u16>,
524) -> u16 {
525    if let Some(params) = state.fns[fn_id].declared_params()
526        && !state.fns[fn_id]
527            .impls
528            .iter()
529            .any(|fn_impl| fn_impl.indirect)
530    {
531        let span = state.fns[fn_id].name_span;
532        ensure_indirect_impl(fn_id, &params, span, output, v, ctx, state);
533    }
534    let entry_id = state.fn_entry_register(fn_id);
535    let value_id = state.alloc_reg_tgt(tgt_id);
536    output.push(Instr::EmptyFnValue(value_id));
537    output.push(Instr::Push(value_id, entry_id));
538    value_id
539}
540
541/// Whether a collection literal builds its elements as function values: it
542/// holds a function at all. A single one is enough, since a function only ever
543/// called by its name leaves nothing in a register for the collection to carry,
544/// and what the collection holds is what a program reads back out of it.
545fn holds_functions(types: &[DataType]) -> bool {
546    types
547        .iter()
548        .any(|t| matches!(t, DataType::Fn(_) | DataType::FnValue(_)))
549}
550
551/// Compiles one element of a collection literal, as a function value where the
552/// collection holds functions and as itself everywhere else.
553fn compile_element(
554    elem: &Expr,
555    as_fn_value: bool,
556    v: &mut Vec<Variable>,
557    ctx: Ctx,
558    state: &mut State<'_>,
559    output: &mut Vec<Instr>,
560) -> u16 {
561    if as_fn_value {
562        compile_fn_value(elem, v, ctx, state, output, None)
563    } else {
564        elem.compile(v, ctx, state, output, None, false, true)
565            .unwrap_id()
566    }
567}
568
569#[inline(always)]
570fn compile_array_literal(
571    array_items: &[Expr],
572    spans: &[Span],
573    v: &mut Vec<Variable>,
574    ctx: Ctx,
575    state: &mut State<'_>,
576    output: &mut Vec<Instr>,
577) -> u16 {
578    if let Some(first) = array_items.first() {
579        let first_type = first.infer_type(v, ctx, state);
580        if let Some(failing_elem_idx) = array_items
581            .iter()
582            .skip(1)
583            .position(|x| x.infer_type(v, ctx, state) != first_type)
584        {
585            let failing_elem_type = array_items[failing_elem_idx + 1].infer_type(v, ctx, state);
586            let failing_elem_span = spans[failing_elem_idx + 2];
587            compiler_errors::error_array_diff_types(
588                ctx.file_idx,
589                state.sources,
590                spans[1],
591                &first_type,
592                failing_elem_span,
593                &failing_elem_type,
594                state.type_names(),
595            )
596        }
597    }
598    // A list that holds functions holds function values: no element is the
599    // function the list holds, so each one carries its own identity for a call
600    // through the list to dispatch on and for a print to read it by.
601    let elem_types = array_items
602        .iter()
603        .map(|elem| elem.infer_type(v, ctx, state))
604        .collect::<Vec<DataType>>();
605    let as_fn_values = holds_functions(&elem_types);
606    let array_id = {
607        state.pools.objs.push(Vec::with_capacity(array_items.len()));
608        state.pools.objs.len() - 1
609    };
610    if array_items.is_empty() && !ctx.single_run {
611        let array_reg = {
612            state.registers.push(Data::array(array_id as u32));
613            state.registers.len() - 1
614        } as u16;
615        output.push(Instr::EmptyArray(array_reg));
616        return array_reg;
617    }
618    if ctx.single_run {
619        for elem in array_items {
620            let id = compile_element(elem, as_fn_values, v, ctx, state, output);
621            if elem.is_constant_literal() {
622                state
623                    .pools
624                    .objs
625                    .get_mut(array_id)
626                    .push(state.registers[id as usize]);
627            } else {
628                output.push(Instr::ObjElemMov(
629                    id,
630                    array_id as u16,
631                    state.pools.objs[array_id].len() as u16,
632                ));
633                state.pools.objs.get_mut(array_id).push(NULL);
634            }
635        }
636        state.registers.push(Data::array(array_id as u32));
637        (state.registers.len() - 1) as u16
638    } else {
639        // Check if all elements are constant (no instructions emitted)
640        let mut constant_array = true;
641        let mut elem_ids: Vec<u16> = Vec::with_capacity(array_items.len());
642        for elem in array_items {
643            let id = compile_element(elem, as_fn_values, v, ctx, state, output);
644            if elem.is_constant_literal() {
645                state
646                    .pools
647                    .objs
648                    .get_mut(array_id)
649                    .push(state.registers[id as usize]);
650            } else {
651                constant_array = false;
652                state.pools.objs.get_mut(array_id).push(NULL);
653            }
654            elem_ids.push(id);
655        }
656
657        if constant_array {
658            // The template array is held by a register to prevent it from being freed by the GC
659            let template_reg = state.template_register(Data::array(array_id as u32));
660            // `CloneArray` gives the destination its own pool entry and
661            // overwrites the register, so what sits there until then is only a
662            // placeholder. It names this literal's own template entry rather
663            // than pool slot 0, which belongs to whatever object the program
664            // built first: a placeholder aimed at slot 0 makes anything that
665            // reads the register file before execution, the debug register
666            // dump included, show that object's contents in a register about
667            // to hold this array, and keeps slot 0 reachable from the register
668            // file for as long as the register is unwritten.
669            let dest_reg = {
670                state.registers.push(Data::array(array_id as u32));
671                (state.registers.len() - 1) as u16
672            };
673            output.push(Instr::CloneArray(
674                template_reg,
675                dest_reg,
676                state.pools.objs[array_id].len() as u16,
677            ));
678            dest_reg
679        } else {
680            // `EmptyArray` allocates the destination's entry, so the same
681            // placeholder rule applies. This branch emits no template
682            // instruction, but the literal still owns the pool entry reserved
683            // for it above, which holds its constant elements and a null per
684            // dynamic one: the closest value of the right shape it has, and no
685            // root the literal was not already holding.
686            let dest_reg = {
687                state.registers.push(Data::array(array_id as u32));
688                (state.registers.len() - 1) as u16
689            };
690            output.push(Instr::EmptyArray(dest_reg));
691            for elem_reg in elem_ids {
692                output.push(Instr::Push(dest_reg, elem_reg));
693            }
694            dest_reg
695        }
696    }
697}
698
699/// Checks a struct field's value against the type the field declares, and
700/// answers whether the value is built as a function value. A field declared
701/// `fn(...)` holds a function value, so the function written into it is
702/// compiled at the types the field declares, and the value carries which
703/// function it is.
704#[allow(clippy::too_many_arguments)]
705fn compile_struct_field_type(
706    name: &SmolStr,
707    struct_idx: usize,
708    field_idx: usize,
709    field_expr: &Expr,
710    field_value_span: Span,
711    v: &mut Vec<Variable>,
712    ctx: Ctx,
713    state: &mut State<'_>,
714    output: &mut Vec<Instr>,
715) -> bool {
716    let field_type = field_expr.infer_type(v, ctx, state);
717    let declared = state.structs[struct_idx].fields[field_idx].1.clone();
718    let as_fn_value = declared_holds_fn_signature(&declared);
719    if as_fn_value {
720        check_declared_fn(
721            field_expr,
722            &declared,
723            field_value_span,
724            output,
725            v,
726            ctx,
727            state,
728        );
729    }
730    let as_fn_value = as_fn_value && matches!(declared, DataType::FnValue(_));
731    // A function was just checked against the declared signature, so it
732    // stands for that; anything else is checked as the type it is.
733    let field_type = if as_fn_value && matches!(field_type, DataType::Fn(_) | DataType::FnValue(_))
734    {
735        declared
736    } else {
737        field_type
738    };
739    let field = &state.structs[struct_idx].fields[field_idx];
740    if !struct_field_type_matches(&field.1, &field_type, state.generics) {
741        compiler_errors::error_struct_field_invalid_type(
742            ctx.file_idx,
743            name,
744            field.2,
745            &field.0,
746            &field.1,
747            field_value_span,
748            &field_type,
749            state.sources,
750            state.type_names(),
751        );
752    }
753    as_fn_value
754}
755
756/// Where a struct literal takes the fields it does not write from.
757#[derive(Clone, Copy)]
758enum StructFill {
759    /// Nowhere: every field has to be written.
760    Nothing,
761    /// The struct value in this register, a `..base`.
762    Base(u16),
763    /// The values the fields were declared with, or the empty value of each
764    /// field's type: `S::default()` and a bare `..`.
765    Defaults,
766}
767
768#[allow(clippy::too_many_arguments)]
769fn compile_struct_literal(
770    namespace: &[SmolStr],
771    fields: &[(SmolStr, Expr, Span, Span)],
772    type_args: &[TypeExpr],
773    base: Option<&(StructRest, Span)>,
774    span: Span,
775    v: &mut Vec<Variable>,
776    ctx: Ctx,
777    state: &mut State<'_>,
778    output: &mut Vec<Instr>,
779) -> u16 {
780    let expected_struct_idx = literal_struct_id(
781        namespace,
782        fields,
783        type_args,
784        base.and_then(|(b, _)| b.base()),
785        span,
786        v,
787        ctx,
788        state,
789    ) as usize;
790    let name = state.structs[expected_struct_idx].name.clone();
791    let expected_fields_len = state.structs[expected_struct_idx].fields.len();
792    if base.is_some() {
793        // A literal with a base need not write every field, so a count cannot
794        // catch a field the struct does not declare; each is looked up.
795        if let Some(unexpected_field) = fields.iter().find(|(f, _, _, _)| {
796            !state.structs[expected_struct_idx]
797                .fields
798                .iter()
799                .any(|(declared, _, _)| declared == f)
800        }) {
801            compiler_errors::error_struct_no_such_field(
802                ctx.file_idx,
803                &name,
804                state.structs[expected_struct_idx].name_span,
805                unexpected_field.2,
806                &unexpected_field.0,
807                state.sources,
808            )
809        }
810    } else if expected_fields_len < fields.len() {
811        let unexpected_field = &fields[expected_fields_len];
812        compiler_errors::error_struct_no_such_field(
813            ctx.file_idx,
814            &name,
815            state.structs[expected_struct_idx].name_span,
816            unexpected_field.2,
817            &unexpected_field.0,
818            state.sources,
819        )
820    }
821    let fill = match base {
822        None => StructFill::Nothing,
823        Some((StructRest::Default, _)) => {
824            match user_struct_default(expected_struct_idx as u16, state) {
825                Some(fn_id) => StructFill::Base(call_user_struct_default(
826                    fn_id,
827                    expected_struct_idx as u16,
828                    span,
829                    v,
830                    ctx,
831                    state,
832                    output,
833                )),
834                None => StructFill::Defaults,
835            }
836        }
837        Some((StructRest::Base(base), base_span)) => {
838            let base_type = base.infer_type(v, ctx, state);
839            if base_type != DataType::Struct(state.structs[expected_struct_idx].id) {
840                compiler_errors::error_struct_base_type(
841                    ctx.file_idx,
842                    &name,
843                    *base_span,
844                    &base_type,
845                    state.sources,
846                    state.type_names(),
847                );
848            }
849            StructFill::Base(
850                base.compile(v, ctx, state, output, None, false, true)
851                    .unwrap_id(),
852            )
853        }
854    };
855    let dest = build_struct(
856        expected_struct_idx,
857        fields,
858        fill,
859        span,
860        v,
861        ctx,
862        state,
863        output,
864    );
865    if let StructFill::Base(base_reg) = fill {
866        state.free_reg(base_reg, v);
867    }
868    dest
869}
870
871/// The `default` function the `impl` block of struct `struct_idx` declares,
872/// which replaces the default the field declarations give. Inside that
873/// function's own body the struct's default is the one the fields give, so the
874/// function can start from a bare `..` or `S::default()` without calling
875/// itself.
876fn user_struct_default(struct_idx: u16, state: &State<'_>) -> Option<usize> {
877    let name = mangle_method(
878        &state.structs[struct_idx as usize].name,
879        STRUCT_DEFAULT_METHOD,
880    );
881    let fn_id = state.fns.iter().position(|f| f.name == name)?;
882    (body_being_compiled() != Some(fn_id)).then_some(fn_id)
883}
884
885/// Calls the `default` function of struct `struct_idx`, checking first that
886/// it builds that struct: a function declared without `-> Type` is checked
887/// against what its body returns.
888fn call_user_struct_default(
889    fn_id: usize,
890    struct_idx: u16,
891    span: Span,
892    v: &mut Vec<Variable>,
893    ctx: Ctx,
894    state: &mut State<'_>,
895    output: &mut Vec<Instr>,
896) -> u16 {
897    let name = state.fns[fn_id].name.clone();
898    check_struct_default_fn(
899        &state.fns[fn_id],
900        struct_idx,
901        None,
902        state.sources,
903        state.type_names(),
904    );
905    let (returned, returned_span) = match state.fns[fn_id].return_type.clone() {
906        Some(declared) => declared,
907        None => (
908            infer_user_fn_return_type(fn_id, &[], &[], &name, v, ctx, state),
909            state.fns[fn_id].name_span,
910        ),
911    };
912    check_struct_default_fn(
913        &state.fns[fn_id],
914        struct_idx,
915        Some((&returned, returned_span)),
916        state.sources,
917        state.type_names(),
918    );
919    handle_user_function(
920        &name,
921        fn_id,
922        output,
923        v,
924        ctx,
925        state,
926        None,
927        &[],
928        span,
929        &[],
930        &[],
931        None,
932    )
933    .unwrap_id()
934}
935
936/// Lowers the default value of struct `struct_idx`: `S::default()`, and a
937/// field of a struct type a default leaves unwritten. A `default` function in
938/// the struct's `impl` block builds it when there is one.
939pub(crate) fn compile_struct_default(
940    struct_idx: u16,
941    span: Span,
942    v: &mut Vec<Variable>,
943    ctx: Ctx,
944    state: &mut State<'_>,
945    output: &mut Vec<Instr>,
946) -> u16 {
947    thread_local! {
948        /// The structs whose defaults are being built, innermost last: a
949        /// struct reached again through its own fields has no finite default.
950        static DEFAULTING: RefCell<Vec<u16>> = const { RefCell::new(Vec::new()) };
951    }
952    /// Pops the struct it pushed, on the way out of a compile error too.
953    struct Leave;
954    impl Drop for Leave {
955        fn drop(&mut self) {
956            DEFAULTING.with(|d| d.borrow_mut().pop());
957        }
958    }
959    if let Some(fn_id) = user_struct_default(struct_idx, state) {
960        return call_user_struct_default(fn_id, struct_idx, span, v, ctx, state, output);
961    }
962    if DEFAULTING.with(|d| d.borrow().contains(&struct_idx)) {
963        let s = &state.structs[struct_idx as usize];
964        let (field_name, field_type, _) = s
965            .fields
966            .iter()
967            .find(|(_, t, _)| matches!(t, DataType::Struct(_)))
968            .cloned()
969            .unwrap_or_else(|| (s.name.clone(), DataType::Struct(struct_idx), span));
970        compiler_errors::error_struct_field_no_default(
971            ctx.file_idx,
972            span,
973            &s.name,
974            &field_name,
975            &field_type,
976            state.sources,
977            state.type_names(),
978        );
979    }
980    DEFAULTING.with(|d| d.borrow_mut().push(struct_idx));
981    let _leave = Leave;
982    build_struct(
983        struct_idx as usize,
984        &[],
985        StructFill::Defaults,
986        span,
987        v,
988        ctx,
989        state,
990        output,
991    )
992}
993
994/// The value an unwritten field of a defaulted struct starts with when its
995/// declaration gives none: the empty value of its type. `None` for a type that
996/// has no empty value, an enum or a function.
997fn zero_value(field_type: &DataType, span: Span) -> Option<Expr> {
998    Some(match field_type {
999        DataType::Int => Expr::Int(0),
1000        DataType::Float => Expr::Float(0.0),
1001        DataType::Bool => Expr::Bool(false),
1002        DataType::String => Expr::String(SmolStr::default()),
1003        DataType::Null | DataType::Unknown => Expr::Null,
1004        DataType::Union(members) if members.contains(&DataType::Null) => Expr::Null,
1005        DataType::Array(_) => Expr::Array(Box::from([]), Box::from([span])),
1006        DataType::Map(_) => Expr::Map(Box::from([]), span),
1007        DataType::Struct(id) => Expr::StructDefault(*id, span),
1008        DataType::Union(_) | DataType::Enum(_) | DataType::Fn(_) | DataType::FnValue(_) => {
1009            return None;
1010        }
1011    })
1012}
1013
1014/// Builds a value of struct `struct_idx` from the fields a literal writes and,
1015/// for the rest, from `fill`.
1016#[allow(clippy::too_many_arguments)]
1017fn build_struct(
1018    struct_idx: usize,
1019    fields: &[(SmolStr, Expr, Span, Span)],
1020    fill: StructFill,
1021    span: Span,
1022    v: &mut Vec<Variable>,
1023    ctx: Ctx,
1024    state: &mut State<'_>,
1025    output: &mut Vec<Instr>,
1026) -> u16 {
1027    let name = state.structs[struct_idx].name.clone();
1028    let type_id = state.structs[struct_idx].id;
1029    let expected_fields_len = state.structs[struct_idx].fields.len();
1030    let pool_idx = {
1031        state
1032            .pools
1033            .objs
1034            .push(Vec::with_capacity(expected_fields_len));
1035        state.pools.objs.len() - 1
1036    };
1037    // Fields whose value is only known when the program runs, as
1038    // `(register, field index)`, and the registers this literal took for
1039    // itself and hands back once the values are stored.
1040    let mut dynamic: Vec<(u16, u16)> = Vec::with_capacity(expected_fields_len);
1041    let mut scratch: Vec<u16> = Vec::new();
1042    for field_idx in 0..expected_fields_len {
1043        let field_name = state.structs[struct_idx].fields[field_idx].0.clone();
1044        let written = fields.iter().find(|(f, _, _, _)| *f == field_name);
1045        let (id, constant) = match (written, fill) {
1046            (Some((_, field_expr, _, field_value_span)), _) => {
1047                let as_fn_value = compile_struct_field_type(
1048                    &name,
1049                    struct_idx,
1050                    field_idx,
1051                    field_expr,
1052                    *field_value_span,
1053                    v,
1054                    ctx,
1055                    state,
1056                    output,
1057                );
1058                let id = compile_element(field_expr, as_fn_value, v, ctx, state, output);
1059                (id, field_expr.is_constant_literal())
1060            }
1061            (None, StructFill::Base(base_reg)) => {
1062                let id = state.alloc_reg();
1063                output.push(Instr::GetFieldStruct(base_reg, field_idx as u16, id));
1064                scratch.push(id);
1065                (id, false)
1066            }
1067            (None, StructFill::Defaults) => {
1068                let field_type = state.structs[struct_idx].fields[field_idx].1.clone();
1069                // A declared value compiles in the file that declared it, where
1070                // no variable of the literal's scope is visible; the empty value
1071                // of the type stands in where none was declared.
1072                let (file_idx, value, value_span) = match state
1073                    .generics
1074                    .struct_default(type_id, field_idx)
1075                {
1076                    Some((file_idx, value, value_span)) => (file_idx, value.clone(), value_span),
1077                    None => (
1078                        ctx.file_idx,
1079                        zero_value(&field_type, span).unwrap_or_else(|| {
1080                            compiler_errors::error_struct_field_no_default(
1081                                ctx.file_idx,
1082                                span,
1083                                &name,
1084                                &field_name,
1085                                &field_type,
1086                                state.sources,
1087                                state.type_names(),
1088                            )
1089                        }),
1090                        span,
1091                    ),
1092                };
1093                let default_ctx = Ctx { file_idx, ..ctx };
1094                let mut scope: Vec<Variable> = Vec::new();
1095                let as_fn_value = compile_struct_field_type(
1096                    &name,
1097                    struct_idx,
1098                    field_idx,
1099                    &value,
1100                    value_span,
1101                    &mut scope,
1102                    default_ctx,
1103                    state,
1104                    output,
1105                );
1106                let id =
1107                    compile_element(&value, as_fn_value, &mut scope, default_ctx, state, output);
1108                (id, value.is_constant_literal())
1109            }
1110            (None, StructFill::Nothing) => {
1111                let missing_elems = state.structs[struct_idx]
1112                    .fields
1113                    .iter()
1114                    .map(|(f, _, _)| f)
1115                    .filter(|f| !fields.iter().any(|(written, _, _, _)| written == *f))
1116                    .collect::<Vec<&SmolStr>>();
1117                compiler_errors::error_struct_missing_fields(
1118                    ctx.file_idx,
1119                    state.structs[struct_idx].name_span,
1120                    span,
1121                    state.sources,
1122                    &missing_elems,
1123                )
1124            }
1125        };
1126        if constant {
1127            let value = state.registers[id as usize];
1128            state.pools.objs.get_mut(pool_idx).push(value);
1129        } else if ctx.single_run {
1130            output.push(Instr::ObjElemMov(
1131                id,
1132                pool_idx as u16,
1133                state.pools.objs[pool_idx].len() as u16,
1134            ));
1135            state.pools.objs.get_mut(pool_idx).push(NULL);
1136        } else {
1137            state.pools.objs.get_mut(pool_idx).push(NULL);
1138            dynamic.push((id, field_idx as u16));
1139        }
1140    }
1141
1142    let dest_reg = if ctx.single_run {
1143        state
1144            .registers
1145            .push(Data::struct_instance(type_id, pool_idx as u32));
1146        (state.registers.len() - 1) as u16
1147    } else {
1148        let template_reg = state.template_register(Data::struct_instance(type_id, pool_idx as u32));
1149        // `CloneStruct` writes the destination register, so until it runs the
1150        // register holds a placeholder. It names this literal's own template
1151        // entry, not pool slot 0, so a read of the register file before
1152        // execution sees this struct's fields instead of the first object the
1153        // program built dressed up as one.
1154        let dest_reg = {
1155            state
1156                .registers
1157                .push(Data::struct_instance(type_id, pool_idx as u32));
1158            (state.registers.len() - 1) as u16
1159        };
1160        output.push(Instr::CloneStruct(template_reg, dest_reg));
1161        for (val_reg, slot) in dynamic {
1162            output.push(Instr::SetFieldStruct(dest_reg, val_reg, slot));
1163        }
1164        dest_reg
1165    };
1166    for id in scratch {
1167        state.free_reg(id, v);
1168    }
1169    dest_reg
1170}
1171
1172/// Resolves a call/reference path to an enum variant `(enum_id, variant_idx)`,
1173/// if it names one. A qualified path (`Color::Red`, `mod::Color::Red`) resolves
1174/// the enum by its leading segments and the variant by the last segment; a bare
1175/// name (`Some`, `None`) resolves by searching every registered enum for a
1176/// variant with that name, first match winning. Never raises a compile error,
1177/// so callers use it to intercept otherwise-unknown call/reference paths.
1178pub(crate) fn resolve_enum_variant(
1179    path: &[SmolStr],
1180    file_idx: u16,
1181    state: &State<'_>,
1182) -> Option<(u16, u16)> {
1183    if path.len() >= 2 {
1184        let variant = &path[path.len() - 1];
1185        let enum_name = &path[path.len() - 2];
1186        let module = &path[..path.len() - 2];
1187        let eid = state.scope(file_idx).find_enum(module, enum_name)?;
1188        let vidx = state.enums[eid]
1189            .variants
1190            .iter()
1191            .position(|vt| &vt.name == variant)?;
1192        Some((eid as u16, vidx as u16))
1193    } else if let Some(name) = path.first() {
1194        for e in state.enums.iter() {
1195            if let Some(vidx) = e.variants.iter().position(|vt| &vt.name == name) {
1196                return Some((e.id, vidx as u16));
1197            }
1198        }
1199        None
1200    } else {
1201        None
1202    }
1203}
1204
1205/// The enum and variant a constructor names, for the lowering.
1206///
1207/// A generic enum has no registered type until something names one, so the
1208/// arguments are typed first and the enum is instantiated at what its payload
1209/// binds; see [`variant_constructor_at`]. A plain enum resolves by name.
1210pub(crate) fn variant_constructor(
1211    path: &[SmolStr],
1212    args: &[Expr],
1213    span: Span,
1214    v: &mut Vec<Variable>,
1215    ctx: Ctx,
1216    state: &mut State<'_>,
1217) -> Option<(u16, u16)> {
1218    let arg_types: Vec<DataType> = args.iter().map(|a| a.infer_type(v, ctx, state)).collect();
1219    variant_constructor_at(path, &arg_types, span, ctx, state)
1220}
1221
1222/// Lowers an enum-variant construction (`Color::Red`, `Some(x)`) to a fresh
1223/// enum value. The object-pool template holds the variant tag at element 0 and
1224/// the payload at elements `1..`; constant payloads are baked into the template
1225/// and dynamic ones are written after a `CloneEnum` with `SetFieldStruct`,
1226/// mirroring how a struct literal is built.
1227#[allow(clippy::too_many_arguments)]
1228pub(crate) fn compile_enum_construction(
1229    enum_id: u16,
1230    variant_idx: u16,
1231    args: &[Expr],
1232    span: Span,
1233    args_indexes: &[Span],
1234    v: &mut Vec<Variable>,
1235    ctx: Ctx,
1236    state: &mut State<'_>,
1237    output: &mut Vec<Instr>,
1238) -> u16 {
1239    let variant = &state.enums[enum_id as usize].variants[variant_idx as usize];
1240    let variant_name = variant.name.clone();
1241    let payload_types = variant.payload.clone();
1242    let arity = payload_types.len();
1243
1244    compiler_errors::check_args(
1245        args,
1246        arity,
1247        &variant_name,
1248        span,
1249        state.sources,
1250        ctx.file_idx,
1251    );
1252    // A payload is checked the way a function parameter is: an instantiation
1253    // whose type arguments are `any` lines up with a named one, so a bare
1254    // payload-less variant (`Leaf`, which pins nothing and is a `Tree<any>`)
1255    // reaches a slot declared `Tree<int>`.
1256    for (i, expected) in payload_types.iter().enumerate() {
1257        let received = args[i].infer_type(v, ctx, state);
1258        if !param_type_matches(expected, &received, state.generics) {
1259            compiler_errors::error_function_arg_invalid_type(
1260                &received,
1261                expected,
1262                args_indexes[i],
1263                &variant_name,
1264                None,
1265                ctx.file_idx,
1266                state.sources,
1267                state.type_names(),
1268            );
1269        }
1270    }
1271
1272    let pool_idx = {
1273        state.pools.objs.push(Vec::with_capacity(arity + 1));
1274        state.pools.objs.len() - 1
1275    };
1276    state
1277        .pools
1278        .objs
1279        .get_mut(pool_idx)
1280        .push(Data::int(i64::from(variant_idx)));
1281
1282    let mut dynamic: Vec<(u16, u16)> = Vec::with_capacity(arity);
1283    for (i, arg) in args.iter().enumerate() {
1284        let id = arg
1285            .compile(v, ctx, state, output, None, false, true)
1286            .unwrap_id();
1287        if arg.is_constant_literal() {
1288            let d = state.registers[id as usize];
1289            state.pools.objs.get_mut(pool_idx).push(d);
1290        } else {
1291            state.pools.objs.get_mut(pool_idx).push(NULL);
1292            dynamic.push((id, (i + 1) as u16));
1293        }
1294    }
1295
1296    let template_reg = state.template_register(Data::enum_instance(enum_id, pool_idx as u32));
1297    // `CloneEnum` allocates the destination's own pool entry and overwrites the
1298    // register, so what sits there until then is only a placeholder. It aims at
1299    // the template's entry rather than at pool slot 0, which belongs to
1300    // whatever object the program built first: a placeholder aimed at slot 0
1301    // reads that object's first word as this enum's variant tag, which is how
1302    // the debug register dump came to format an array element as a tag. Aiming
1303    // at the template makes the placeholder a valid value of the right enum, so
1304    // anything that reads the register file before execution sees the variant
1305    // the register is about to hold.
1306    let dest_reg = {
1307        state
1308            .registers
1309            .push(Data::enum_instance(enum_id, pool_idx as u32));
1310        (state.registers.len() - 1) as u16
1311    };
1312    output.push(Instr::CloneEnum(template_reg, dest_reg));
1313    for (val_reg, slot) in dynamic {
1314        output.push(Instr::SetFieldStruct(dest_reg, val_reg, slot));
1315    }
1316    dest_reg
1317}
1318
1319/// Registers a nested `enum` declaration (one inside a function body). Top-level
1320/// enums are pre-registered by `parse_toplevel`; this mirrors
1321/// `compile_struct_definition` for the nested case.
1322fn compile_enum_definition(
1323    name: &SmolStr,
1324    variants: &[(SmolStr, Box<[TypeExpr]>, Span)],
1325    span: Span,
1326    type_params: &TypeParams,
1327    ctx: Ctx,
1328    state: &mut State<'_>,
1329) {
1330    if !type_params.is_empty() {
1331        let template = state.generics.add_enum_template(
1332            name.clone(),
1333            type_params.clone(),
1334            ctx.file_idx,
1335            Box::from(variants),
1336        );
1337        state
1338            .scope_mut(ctx.file_idx)
1339            .symbols
1340            .push((name.clone(), SymbolKind::Template(template)));
1341        return;
1342    }
1343    let enum_id = state.enums.len() as u16;
1344    state.enums.push(EnumType {
1345        name: name.clone(),
1346        variants: Box::from([]),
1347        id: enum_id,
1348        name_span: span,
1349    });
1350    state
1351        .scope_mut(ctx.file_idx)
1352        .symbols
1353        .push((name.clone(), SymbolKind::Enum(enum_id)));
1354    let resolved = variants
1355        .iter()
1356        .map(|(vn, payload, vspan)| EnumVariant {
1357            name: vn.clone(),
1358            payload: payload
1359                .iter()
1360                .map(|t| t.to_datatype(&mut state.type_ctx(ctx.file_idx)))
1361                .collect(),
1362            name_span: *vspan,
1363        })
1364        .collect();
1365    state.enums[enum_id as usize].variants = resolved;
1366}
1367
1368/// Extracts a match arm's variant pattern: the variant index within `enum_id`
1369/// and the payload binder identifiers (`_` ignores a slot). Raises a compile
1370/// error for an unknown variant, a wrong-arity pattern, or a non-identifier
1371/// binder.
1372///
1373/// A qualified pattern resolves through its qualifier, the way a qualified
1374/// construction does: `Shape::Circle(r)` finds `Shape` in the arm's scope,
1375/// `sh::Shape::Circle(r)` walks the alias, and `Slot<int>::Empty` instantiates
1376/// the generic enum. The enum that comes back has to be the scrutinee's, so a
1377/// pattern qualified with another enum is reported instead of matching the
1378/// variant of the same name. Only a bare `Circle(r)` is looked up by name in
1379/// the scrutinee's enum.
1380pub(crate) fn resolve_variant_pattern(
1381    enum_id: u16,
1382    pattern: &Expr,
1383    fallback_span: Span,
1384    ctx: Ctx,
1385    state: &mut State<'_>,
1386) -> (u16, Vec<SmolStr>) {
1387    let (path, type_args, binders, span): (&[SmolStr], &[TypeExpr], Vec<SmolStr>, Span) =
1388        match pattern {
1389            Expr::Var(name, span) => (std::slice::from_ref(name), &[], Vec::new(), *span),
1390            Expr::NamespacedRef(path, span, type_args) => (path, type_args, Vec::new(), *span),
1391            Expr::FunctionCall(args, namespace, span, _, type_args) => {
1392                let mut binders = Vec::with_capacity(args.len());
1393                for arg in args {
1394                    if let Expr::Var(binder, _) = arg {
1395                        binders.push(binder.clone());
1396                    } else {
1397                        compiler_errors::error_enum(
1398                            "Invalid match pattern",
1399                            "Enum variant patterns may only bind identifiers, e.g. Circle(r)",
1400                            *span,
1401                            ctx.file_idx,
1402                            state.sources,
1403                        );
1404                    }
1405                }
1406                (&**namespace, type_args, binders, *span)
1407            }
1408            _ => compiler_errors::error_enum(
1409                "Invalid match pattern",
1410                "A match on an enum expects variant patterns, e.g. Circle(r) or Unit",
1411                fallback_span,
1412                ctx.file_idx,
1413                state.sources,
1414            ),
1415        };
1416    let variant_name = path[path.len() - 1].clone();
1417    let variant_idx = if path.len() >= 2 {
1418        let (pattern_enum, variant_idx) = if type_args.is_empty() {
1419            resolve_qualified_pattern_enum(path, enum_id, span, ctx, state)
1420        } else {
1421            resolve_generic_variant(path, type_args, span, ctx, state)
1422        };
1423        if pattern_enum != enum_id {
1424            compiler_errors::error_pattern_enum_mismatch(
1425                &path.join("::"),
1426                &state.enums[pattern_enum as usize].name,
1427                &state.enums[enum_id as usize].name,
1428                span,
1429                ctx.file_idx,
1430                state.sources,
1431            );
1432        }
1433        variant_idx
1434    } else {
1435        let e = &state.enums[enum_id as usize];
1436        let Some(variant_idx) = e.variants.iter().position(|vt| vt.name == variant_name) else {
1437            compiler_errors::error_enum(
1438                "Unknown enum variant",
1439                &format!("{} is not a variant of enum {}", variant_name, e.name),
1440                span,
1441                ctx.file_idx,
1442                state.sources,
1443            );
1444        };
1445        variant_idx as u16
1446    };
1447    let e = &state.enums[enum_id as usize];
1448    let expected_arity = e.variants[variant_idx as usize].payload.len();
1449    if binders.len() != expected_arity {
1450        compiler_errors::error_enum(
1451            "Wrong variant payload arity",
1452            &format!(
1453                "Variant {} binds {} value(s) but the pattern has {}",
1454                variant_name,
1455                expected_arity,
1456                binders.len()
1457            ),
1458            span,
1459            ctx.file_idx,
1460            state.sources,
1461        );
1462    }
1463    (variant_idx, binders)
1464}
1465
1466/// The enum and variant a qualified pattern with no type arguments names.
1467///
1468/// The segments in front of the variant are the enum name and, before it, the
1469/// module path it is reached through, so a qualifier that leads nowhere is an
1470/// unknown enum rather than a pattern silently taken apart by its last segment.
1471fn resolve_qualified_pattern_enum(
1472    path: &[SmolStr],
1473    scrut_enum: u16,
1474    span: Span,
1475    ctx: Ctx,
1476    state: &State<'_>,
1477) -> (u16, u16) {
1478    let variant = &path[path.len() - 1];
1479    let enum_name = &path[path.len() - 2];
1480    let module = &path[..path.len() - 2];
1481    let Some(pattern_enum) = state.scope(ctx.file_idx).find_enum(module, enum_name) else {
1482        compiler_errors::error_enum(
1483            "Unknown enum",
1484            &format!(
1485                "{} does not name an enum in scope, and this match is on enum {}",
1486                path[..path.len() - 1].join("::"),
1487                state.enums[scrut_enum as usize].name
1488            ),
1489            span,
1490            ctx.file_idx,
1491            state.sources,
1492        );
1493    };
1494    let e = &state.enums[pattern_enum];
1495    let Some(variant_idx) = e.variants.iter().position(|vt| &vt.name == variant) else {
1496        compiler_errors::error_enum(
1497            "Unknown enum variant",
1498            &format!("{} is not a variant of enum {}", variant, e.name),
1499            span,
1500            ctx.file_idx,
1501            state.sources,
1502        );
1503    };
1504    (pattern_enum as u16, variant_idx as u16)
1505}
1506
1507/// The span a variant pattern was written at, or `fallback` for one that
1508/// carries none of its own.
1509const fn pattern_span(pattern: &Expr, fallback: Span) -> Span {
1510    match pattern {
1511        Expr::Var(_, span)
1512        | Expr::NamespacedRef(_, span, _)
1513        | Expr::FunctionCall(_, _, span, _, _) => *span,
1514        _ => fallback,
1515    }
1516}
1517
1518/// The names one resolved alternative binds, each with the type of the payload
1519/// it binds, in name order.
1520pub(crate) fn alternative_bindings(
1521    enum_id: u16,
1522    (variant_idx, binders): &(u16, Vec<SmolStr>),
1523    state: &State<'_>,
1524) -> Vec<(SmolStr, DataType)> {
1525    let payload = &state.enums[enum_id as usize].variants[*variant_idx as usize].payload;
1526    let mut bound: Vec<(SmolStr, DataType)> = binders
1527        .iter()
1528        .zip(payload.iter())
1529        .filter(|(binder, _)| binder.as_str() != "_")
1530        .map(|(binder, ty)| (binder.clone(), ty.clone()))
1531        .collect();
1532    bound.sort_by(|a, b| a.0.cmp(&b.0));
1533    bound
1534}
1535
1536/// Resolves every `|` alternative of an enum arm to the variant it names and
1537/// the names it binds, one per payload position.
1538///
1539/// The arm body reads its bindings whichever alternative matched, so every
1540/// alternative has to bind the same names at the same types; one that does
1541/// not is reported against itself.
1542pub(crate) fn resolve_arm_patterns(
1543    enum_id: u16,
1544    patterns: &[Expr],
1545    span: Span,
1546    ctx: Ctx,
1547    state: &mut State<'_>,
1548) -> Vec<(u16, Vec<SmolStr>)> {
1549    let alternatives: Vec<(u16, Vec<SmolStr>)> = patterns
1550        .iter()
1551        .map(|pattern| resolve_variant_pattern(enum_id, pattern, span, ctx, state))
1552        .collect();
1553    if let [first, rest @ ..] = alternatives.as_slice() {
1554        let expected = alternative_bindings(enum_id, first, state);
1555        for (k, alternative) in rest.iter().enumerate() {
1556            let found = alternative_bindings(enum_id, alternative, state);
1557            if found != expected {
1558                compiler_errors::error_pattern_bindings_differ(
1559                    &expected,
1560                    &found,
1561                    pattern_span(&patterns[k + 1], span),
1562                    ctx.file_idx,
1563                    state.sources,
1564                    state.type_names(),
1565                );
1566            }
1567        }
1568    }
1569    alternatives
1570}
1571
1572/// Declares, for the type walks that read an arm body without compiling it,
1573/// the names the arm's pattern binds. Every alternative binds the same ones,
1574/// so the first says what they are.
1575pub(crate) fn declare_arm_bindings(
1576    enum_id: u16,
1577    patterns: &[Expr],
1578    span: Span,
1579    v: &mut Vec<Variable>,
1580    ctx: Ctx,
1581    state: &mut State<'_>,
1582) {
1583    let alternatives = resolve_arm_patterns(enum_id, patterns, span, ctx, state);
1584    for (name, var_type) in alternative_bindings(enum_id, &alternatives[0], state) {
1585        v.push(Variable {
1586            declared: None,
1587            name,
1588            register_id: 0,
1589            cell: false,
1590            var_type,
1591        });
1592    }
1593}
1594
1595/// Compiles a match arm's body. A statement match runs it; a match used as a
1596/// value runs it into `result`, its last expression being the arm's value.
1597fn compile_arm_body(
1598    body: &[Expr],
1599    result: Option<(u16, bool)>,
1600    v: &mut Vec<Variable>,
1601    ctx: Ctx,
1602    state: &mut State<'_>,
1603    output: &mut Vec<Instr>,
1604) {
1605    if let Some((result, as_fn_value)) = result {
1606        compile_inline_condition_branch(body, v, ctx, state, output, result, as_fn_value);
1607    } else {
1608        let code = compile_expr(body, v, ctx.advance_offset(output.len() as u16), state);
1609        output.extend(code);
1610    }
1611}
1612
1613/// Lowers a `match` on an enum scrutinee to a variant-tag compare chain with
1614/// per-arm payload binding, reusing the ordinary conditional-jump machinery.
1615///
1616/// Each `|` alternative of an arm tests its own variant and binds its own
1617/// payload positions into registers the arm shares, then joins the arm body.
1618/// `result` is the register a match used as a value leaves its value in, with
1619/// whether that value is a function value, and `None` for a statement.
1620#[allow(clippy::too_many_arguments)]
1621fn compile_enum_match(
1622    enum_id: u16,
1623    scrutinee: &Expr,
1624    arms: &[(Box<[Expr]>, Box<[Expr]>)],
1625    wildcard: Option<&[Expr]>,
1626    span: Span,
1627    v: &mut Vec<Variable>,
1628    ctx: Ctx,
1629    state: &mut State<'_>,
1630    output: &mut Vec<Instr>,
1631    result: Option<(u16, bool)>,
1632) {
1633    let scrut_reg = scrutinee
1634        .compile(v, ctx, state, output, None, false, true)
1635        .unwrap_id();
1636    // Root the scrutinee for the whole match so its object-pool payload is not
1637    // reclaimed and its register is not reused across arm bodies.
1638    let v_base = v.len();
1639    v.push(Variable {
1640        declared: None,
1641        name: SmolStr::new_static("[MATCH SCRUT]"),
1642        register_id: scrut_reg,
1643        cell: false,
1644        var_type: DataType::Enum(enum_id),
1645    });
1646
1647    let tag_reg = state.alloc_reg();
1648    output.push(Instr::GetFieldStruct(scrut_reg, 0, tag_reg));
1649
1650    let variant_count = state.enums[enum_id as usize].variants.len();
1651    let mut covered = vec![false; variant_count];
1652    // One entry per alternative: where its test starts and the jump it takes
1653    // when the tag is another variant's.
1654    let mut false_jmps: Vec<usize> = Vec::with_capacity(arms.len());
1655    let mut alternative_starts: Vec<usize> = Vec::with_capacity(arms.len());
1656    let mut end_jmps: Vec<usize> = Vec::with_capacity(arms.len());
1657
1658    for (patterns, body) in arms {
1659        let alternatives = resolve_arm_patterns(enum_id, patterns, span, ctx, state);
1660        // (name, register the body reads, register the payload is read into)
1661        let mut slots: Vec<(SmolStr, u16, u16, DataType, bool)> = Vec::new();
1662        let mut to_body: Vec<usize> = Vec::new();
1663        for (k, (variant_idx, binders)) in alternatives.iter().enumerate() {
1664            covered[*variant_idx as usize] = true;
1665            alternative_starts.push(output.len());
1666            let idx_reg = state.alloc_reg();
1667            output.push(Instr::SetInt(idx_reg, i32::from(*variant_idx)));
1668            false_jmps.push(output.len());
1669            output.push(Instr::NotEqJmp(tag_reg, idx_reg, 0));
1670            state.free_reg(idx_reg, v);
1671
1672            // Bind the variant payload into the arm's locals. The first
1673            // alternative allocates them; the others write the same ones.
1674            for (i, binder) in binders.iter().enumerate() {
1675                if binder.as_str() == "_" {
1676                    continue;
1677                }
1678                let field = (i + 1) as u16;
1679                if let Some((_, read_reg, value_reg, _, captured)) =
1680                    slots.iter().find(|slot| slot.0 == *binder)
1681                {
1682                    if *captured {
1683                        output.push(Instr::GetFieldStruct(scrut_reg, field, *value_reg));
1684                        output.push(Instr::NewCell(*value_reg, *read_reg));
1685                    } else {
1686                        output.push(Instr::GetFieldStruct(scrut_reg, field, *read_reg));
1687                    }
1688                    continue;
1689                }
1690                let value_reg = state.alloc_reg();
1691                output.push(Instr::GetFieldStruct(scrut_reg, field, value_reg));
1692                let payload_type = state.enums[enum_id as usize].variants[*variant_idx as usize]
1693                    .payload[i]
1694                    .clone();
1695                let captured = code_captures_variable(binder, body);
1696                let read_reg = if captured {
1697                    let cell_id = state.alloc_reg();
1698                    output.push(Instr::NewCell(value_reg, cell_id));
1699                    cell_id
1700                } else {
1701                    value_reg
1702                };
1703                slots.push((binder.clone(), read_reg, value_reg, payload_type, captured));
1704            }
1705            if k + 1 < alternatives.len() {
1706                to_body.push(output.len());
1707                output.push(Instr::Jmp(0));
1708            }
1709        }
1710        let body_start = output.len();
1711        for at in to_body {
1712            set_jmp_size(&mut output[at], (body_start - at) as u16);
1713        }
1714
1715        let v_arm = v.len();
1716        for (name, read_reg, _, var_type, captured) in slots {
1717            v.push(Variable {
1718                declared: None,
1719                name,
1720                register_id: read_reg,
1721                cell: captured,
1722                var_type,
1723            });
1724        }
1725        compile_arm_body(body, result, v, ctx, state, output);
1726        v.truncate(v_arm);
1727
1728        end_jmps.push(output.len());
1729        output.push(Instr::Jmp(0));
1730    }
1731
1732    // Where a non-matching last alternative (and the wildcard, if any) begins.
1733    let after_arms = output.len();
1734    if let Some(w) = wildcard {
1735        compile_arm_body(w, result, v, ctx, state, output);
1736    }
1737    let end = output.len();
1738
1739    for (k, &j) in false_jmps.iter().enumerate() {
1740        let target = if k + 1 < alternative_starts.len() {
1741            alternative_starts[k + 1]
1742        } else {
1743            after_arms
1744        };
1745        set_jmp_size(&mut output[j], (target - j) as u16);
1746    }
1747    for &j in &end_jmps {
1748        set_jmp_size(&mut output[j], (end - j) as u16);
1749    }
1750
1751    v.truncate(v_base);
1752    state.free_reg(tag_reg, v);
1753    state.free_reg(scrut_reg, v);
1754
1755    if wildcard.is_none() && !covered.iter().all(|&c| c) {
1756        let missing: Vec<&str> = state.enums[enum_id as usize]
1757            .variants
1758            .iter()
1759            .enumerate()
1760            .filter(|(i, _)| !covered[*i])
1761            .map(|(_, vt)| vt.name.as_str())
1762            .collect();
1763        compiler_errors::error_enum(
1764            "Non-exhaustive match",
1765            &format!(
1766                "match on enum {} does not cover: {}. Add the missing arm(s) or a `_` wildcard",
1767                state.enums[enum_id as usize].name,
1768                missing.join(", ")
1769            ),
1770            span,
1771            ctx.file_idx,
1772            state.sources,
1773        );
1774    }
1775}
1776
1777/// The enum an arm pattern names a variant of, or `None` for a pattern the
1778/// equality lowering compares as an ordinary value. Resolution follows what
1779/// the pattern would compile to: a variable in scope wins over a variant of
1780/// the same name, and a user function wins over a bare variant name.
1781fn pattern_variant_enum(
1782    pattern: &Expr,
1783    v: &[Variable],
1784    ctx: Ctx,
1785    state: &State<'_>,
1786) -> Option<u16> {
1787    let path: &[SmolStr] = match pattern {
1788        Expr::Var(name, _) if v.iter().all(|var| &var.name != name) => std::slice::from_ref(name),
1789        Expr::NamespacedRef(path, _, _) => path,
1790        Expr::FunctionCall(_, namespace, _, _, _)
1791            if namespace.len() >= 2
1792                || namespace.first().is_some_and(|name| {
1793                    state
1794                        .scope(ctx.file_idx)
1795                        .find_function(&[], name.as_str())
1796                        .is_none()
1797                }) =>
1798        {
1799            namespace
1800        }
1801        _ => return None,
1802    };
1803    resolve_enum_variant(path, ctx.file_idx, state).map(|(enum_id, _)| enum_id)
1804}
1805
1806/// Rejects a `match` whose arms name variants of an enum the scrutinee is not.
1807/// Only an enum scrutinee carries the variant tag the arms dispatch on; the
1808/// equality lowering would compile each pattern as a variant construction and
1809/// compare it, which leaves every payload binder unbound.
1810///
1811/// Both the return-flow pass and the lowering call this, because either can be
1812/// the first to read an arm body.
1813pub(crate) fn check_match_scrutinee_is_enum(
1814    scrut_type: &DataType,
1815    arms: &[(Box<[Expr]>, Box<[Expr]>)],
1816    span: Span,
1817    v: &[Variable],
1818    ctx: Ctx,
1819    state: &State<'_>,
1820) {
1821    if let Some(enum_id) = arms
1822        .iter()
1823        .flat_map(|(patterns, _)| patterns.iter())
1824        .find_map(|pattern| pattern_variant_enum(pattern, v, ctx, state))
1825    {
1826        compiler_errors::error_match_not_enum(
1827            &state.enums[enum_id as usize].name,
1828            scrut_type,
1829            span,
1830            ctx.file_idx,
1831            state.sources,
1832            state.type_names(),
1833        );
1834    }
1835}
1836
1837/// The test an arm of an equality `match` runs: the scrutinee, held in the
1838/// variable `scrutinee`, equal to any of the arm's `|` alternatives.
1839fn equality_arm_test(scrutinee: &SmolStr, patterns: &[Expr], span: Span) -> Expr {
1840    let equals = |pattern: &Expr| {
1841        Expr::Eq(
1842            Box::new(Expr::Var(scrutinee.clone(), span)),
1843            Box::new(pattern.clone()),
1844            span,
1845            span,
1846        )
1847    };
1848    let mut test = equals(&patterns[0]);
1849    for pattern in &patterns[1..] {
1850        test = Expr::BoolOr(Box::new(test), Box::new(equals(pattern)), span, span);
1851    }
1852    test
1853}
1854
1855/// The `if` chain an equality `match` runs: one branch per arm, testing the
1856/// scrutinee held in `scrutinee`, then the wildcard as the `else`. The first
1857/// arm's body comes first, the way a condition holds its own code ahead of
1858/// its `else if` blocks.
1859fn equality_match_chain(
1860    scrutinee: &SmolStr,
1861    arms: &[(Box<[Expr]>, Box<[Expr]>)],
1862    wildcard: Option<&[Expr]>,
1863    span: Span,
1864) -> (Expr, Box<[Expr]>) {
1865    let (first_patterns, first_body) = &arms[0];
1866    let mut code: Vec<Expr> = Vec::with_capacity(arms.len() + first_body.len());
1867    code.extend(first_body.iter().cloned());
1868    for (patterns, body) in &arms[1..] {
1869        code.push(Expr::ElseIfBlock(
1870            Box::new(equality_arm_test(scrutinee, patterns, span)),
1871            body.clone(),
1872            span,
1873        ));
1874    }
1875    if let Some(w) = wildcard {
1876        code.push(Expr::ElseBlock(Box::from(w)));
1877    }
1878    (
1879        equality_arm_test(scrutinee, first_patterns, span),
1880        Box::from(code),
1881    )
1882}
1883
1884/// Compiles a `match`. An enum scrutinee dispatches to variant-pattern matching
1885/// with payload binding; any other scrutinee reproduces the equality-chain
1886/// lowering (`scrutinee == pattern` per arm) that `match` has always had.
1887fn compile_match(
1888    scrutinee: &Expr,
1889    arms: &[(Box<[Expr]>, Box<[Expr]>)],
1890    wildcard: Option<&[Expr]>,
1891    span: Span,
1892    v: &mut Vec<Variable>,
1893    ctx: Ctx,
1894    state: &mut State<'_>,
1895    output: &mut Vec<Instr>,
1896) {
1897    let scrut_type = scrutinee.infer_type(v, ctx, state);
1898    if let DataType::Enum(enum_id) = scrut_type {
1899        compile_enum_match(
1900            enum_id, scrutinee, arms, wildcard, span, v, ctx, state, output, None,
1901        );
1902    } else {
1903        check_match_scrutinee_is_enum(&scrut_type, arms, span, v, ctx, state);
1904        let obj_var = SmolStr::new_static("[MATCH TEMP]");
1905        let (test, code) = equality_match_chain(&obj_var, arms, wildcard, span);
1906        let desugared = Expr::EvalBlock(Box::from([
1907            Expr::VarDeclare(obj_var, Box::new(scrutinee.clone()), None),
1908            Expr::Condition(Box::new(test), code, span, span),
1909        ]));
1910        desugared.compile(v, ctx, state, output, None, false, false);
1911    }
1912}
1913
1914/// Compiles a `match` used as a value into `tgt_id`, or a fresh register, and
1915/// returns where the value is.
1916///
1917/// It covers every case the way an `if` used as a value has an `else`: an
1918/// enum match lists every variant or has a `_` arm, and any other match has a
1919/// `_` arm.
1920fn compile_match_value(
1921    scrutinee: &Expr,
1922    arms: &[(Box<[Expr]>, Box<[Expr]>)],
1923    wildcard: Option<&[Expr]>,
1924    span: Span,
1925    v: &mut Vec<Variable>,
1926    ctx: Ctx,
1927    state: &mut State<'_>,
1928    output: &mut Vec<Instr>,
1929    tgt_id: Option<u16>,
1930    as_fn_value: bool,
1931) -> u16 {
1932    let scrut_type = scrutinee.infer_type(v, ctx, state);
1933    if let DataType::Enum(enum_id) = scrut_type {
1934        let result = state.alloc_reg_tgt(tgt_id);
1935        compile_enum_match(
1936            enum_id,
1937            scrutinee,
1938            arms,
1939            wildcard,
1940            span,
1941            v,
1942            ctx,
1943            state,
1944            output,
1945            Some((result, as_fn_value)),
1946        );
1947        return result;
1948    }
1949    check_match_scrutinee_is_enum(&scrut_type, arms, span, v, ctx, state);
1950    if wildcard.is_none() {
1951        compiler_errors::error_match_value_without_wildcard(span, ctx.file_idx, state.sources);
1952    }
1953    let scrut_reg = scrutinee
1954        .compile(v, ctx, state, output, None, false, true)
1955        .unwrap_id();
1956    let obj_var = SmolStr::new_static("[MATCH TEMP]");
1957    let v_base = v.len();
1958    v.push(Variable {
1959        declared: None,
1960        name: obj_var.clone(),
1961        register_id: scrut_reg,
1962        cell: false,
1963        var_type: scrut_type,
1964    });
1965    let (test, code) = equality_match_chain(&obj_var, arms, wildcard, span);
1966    let result = compile_inline_condition(
1967        &test,
1968        &code,
1969        span,
1970        span,
1971        v,
1972        ctx,
1973        state,
1974        output,
1975        tgt_id,
1976        as_fn_value,
1977    );
1978    v.truncate(v_base);
1979    state.free_reg(scrut_reg, v);
1980    result
1981}
1982
1983fn compile_map_literal(
1984    kv_pairs: &[(Expr, Span, Expr, Span)],
1985    map_span: Span,
1986    v: &mut Vec<Variable>,
1987    ctx: Ctx,
1988    state: &mut State<'_>,
1989    output: &mut Vec<Instr>,
1990) -> u16 {
1991    let mut global_key_type: DataType = DataType::Unknown;
1992    let mut global_val_type: DataType = DataType::Unknown;
1993    // A map whose values are functions holds function values, the same as a
1994    // list does: no value is the function the map holds, so a call through a
1995    // key dispatches on what came out of it.
1996    let value_types = kv_pairs
1997        .iter()
1998        .map(|(_, _, val, _)| val.infer_type(v, ctx, state))
1999        .collect::<Vec<DataType>>();
2000    let as_fn_values = holds_functions(&value_types);
2001    let map_id = state.pools.maps.len();
2002    // Entries are inserted in source order and the map keeps that order, so a
2003    // literal prints the way it was written.
2004    state.pools.maps.push(CandelaMap::with_capacity_and_hasher(
2005        kv_pairs.len(),
2006        BuildHasherDefault::default(),
2007    ));
2008    if ctx.single_run {
2009        for (i, (key, key_span, val, val_span)) in kv_pairs.iter().enumerate() {
2010            if let Some((_, repeat_key_span, _, _)) =
2011                kv_pairs.iter().skip(i + 1).find(|(k, _, _, _)| k == key)
2012            {
2013                error_duplicate_map_key(
2014                    *key_span,
2015                    *repeat_key_span,
2016                    map_span,
2017                    ctx.file_idx,
2018                    state.sources,
2019                );
2020            }
2021            let key_t = key.infer_type(v, ctx, state);
2022            let val_t = val.infer_type(v, ctx, state);
2023            if i == 0 {
2024                global_key_type = key_t;
2025                global_val_type = val_t;
2026            } else {
2027                if key_t != global_key_type {
2028                    error_map_diff_types(
2029                        ctx.file_idx,
2030                        state.sources,
2031                        map_span,
2032                        &global_key_type,
2033                        *key_span,
2034                        &key_t,
2035                        state.type_names(),
2036                    )
2037                }
2038                if val_t != global_val_type {
2039                    error_map_diff_types(
2040                        ctx.file_idx,
2041                        state.sources,
2042                        map_span,
2043                        &global_val_type,
2044                        *val_span,
2045                        &val_t,
2046                        state.type_names(),
2047                    )
2048                }
2049            }
2050            let output_len = output.len();
2051            let key_val_id = key
2052                .compile(v, ctx, state, output, None, false, true)
2053                .unwrap_id();
2054            if !(key.is_constant_literal()
2055                || matches!(key, Expr::Array(_, _)) && output_len == output.len())
2056            {
2057                error_not_literal_map_key(*key_span, map_span, ctx.file_idx, state.sources);
2058            }
2059            let key_val = state.registers[key_val_id as usize];
2060            let id = compile_element(val, as_fn_values, v, ctx, state, output);
2061            if val.is_constant_literal() {
2062                map_insert(
2063                    &mut state.pools.maps[map_id],
2064                    key_val,
2065                    state.registers[id as usize],
2066                    &state.pools.strings,
2067                );
2068            } else {
2069                map_insert(
2070                    &mut state.pools.maps[map_id],
2071                    key_val,
2072                    NULL,
2073                    &state.pools.strings,
2074                );
2075                output.push(Instr::MapInsert(
2076                    map_id as u16,
2077                    state.registers.len() as u16,
2078                    id,
2079                ));
2080                state.registers.push(key_val);
2081            }
2082        }
2083        let dest_id = state.registers.len();
2084        state.registers.push(Data::map(map_id as u32));
2085        dest_id as u16
2086    } else {
2087        let mut dynamic: Vec<(Data, u16)> = Vec::with_capacity(kv_pairs.len());
2088        for (i, (key, key_span, val, val_span)) in kv_pairs.iter().enumerate() {
2089            if let Some((_, repeat_key_span, _, _)) =
2090                kv_pairs.iter().skip(i + 1).find(|(k, _, _, _)| k == key)
2091            {
2092                error_duplicate_map_key(
2093                    *key_span,
2094                    *repeat_key_span,
2095                    map_span,
2096                    ctx.file_idx,
2097                    state.sources,
2098                );
2099            }
2100            let key_t = key.infer_type(v, ctx, state);
2101            let val_t = val.infer_type(v, ctx, state);
2102            if i == 0 {
2103                global_key_type = key_t;
2104                global_val_type = val_t;
2105            } else {
2106                if key_t != global_key_type {
2107                    error_map_diff_types(
2108                        ctx.file_idx,
2109                        state.sources,
2110                        map_span,
2111                        &global_key_type,
2112                        *key_span,
2113                        &key_t,
2114                        state.type_names(),
2115                    )
2116                }
2117                if val_t != global_val_type {
2118                    error_map_diff_types(
2119                        ctx.file_idx,
2120                        state.sources,
2121                        map_span,
2122                        &global_val_type,
2123                        *val_span,
2124                        &val_t,
2125                        state.type_names(),
2126                    )
2127                }
2128            }
2129            let output_len = output.len();
2130            let key_val_id = key
2131                .compile(v, ctx, state, output, None, false, true)
2132                .unwrap_id();
2133            if !(key.is_constant_literal()
2134                || matches!(key, Expr::Array(_, _)) && output_len == output.len())
2135            {
2136                error_not_literal_map_key(*key_span, map_span, ctx.file_idx, state.sources);
2137            }
2138            let key_val = state.registers[key_val_id as usize];
2139            let val_id = compile_element(val, as_fn_values, v, ctx, state, output);
2140            if val.is_constant_literal() {
2141                map_insert(
2142                    &mut state.pools.maps[map_id],
2143                    key_val,
2144                    state.registers[val_id as usize],
2145                    &state.pools.strings,
2146                );
2147            } else {
2148                map_insert(
2149                    &mut state.pools.maps[map_id],
2150                    key_val,
2151                    NULL,
2152                    &state.pools.strings,
2153                );
2154                dynamic.push((key_val, val_id));
2155            }
2156        }
2157
2158        let template_reg = state.template_register(Data::map(map_id as u32));
2159        // `CloneMap` writes the destination register, so until it runs the
2160        // register holds a placeholder. It names this literal's own template
2161        // entry, not map-pool slot 0, which belongs to the first map the
2162        // program built.
2163        let dest_reg = {
2164            state.registers.push(Data::map(map_id as u32));
2165            (state.registers.len() - 1) as u16
2166        };
2167        output.push(Instr::CloneMap(template_reg, dest_reg));
2168        for (key_val, val_id) in dynamic {
2169            let key_reg = if let Some(&id) = state.const_registers.get(&key_val) {
2170                id
2171            } else {
2172                let id = state.registers.len() as u16;
2173                state.const_registers.insert(key_val, id);
2174                state.registers.push(key_val);
2175                id
2176            };
2177            output.push(Instr::MapInsertReg(dest_reg, key_reg, val_id));
2178        }
2179        dest_reg
2180    }
2181}
2182
2183fn compile_struct_field_access(
2184    struct_expr: &Expr,
2185    field: &SmolStr,
2186    struct_span: Span,
2187    field_span: Span,
2188    v: &mut Vec<Variable>,
2189    ctx: Ctx,
2190    state: &mut State<'_>,
2191    output: &mut Vec<Instr>,
2192) -> u16 {
2193    let t = struct_expr.infer_type(v, ctx, state);
2194    if let DataType::Struct(s_id) = t {
2195        let s = &state.structs[s_id as usize];
2196        let idx = s
2197            .fields
2198            .iter()
2199            .position(|f| &f.0 == field)
2200            .unwrap_or_else(|| {
2201                compiler_errors::error_struct_unknown_field(
2202                    ctx.file_idx,
2203                    field_span,
2204                    field,
2205                    &s.name,
2206                    &s.fields,
2207                    state.sources,
2208                );
2209            });
2210        let id = struct_expr
2211            .compile(v, ctx, state, output, None, false, true)
2212            .unwrap_id();
2213        let dest_reg_id = state.alloc_reg();
2214        output.push(Instr::GetFieldStruct(id, idx as u16, dest_reg_id));
2215        dest_reg_id
2216    } else {
2217        error_invalid_type(
2218            &DataType::Struct(0),
2219            &t,
2220            struct_span,
2221            None,
2222            None,
2223            ctx.file_idx,
2224            state.sources,
2225            state.type_names(),
2226        );
2227    }
2228}
2229
2230/// `operand?`: the value inside a `Some` or an `Ok`, or a return of the `None`
2231/// or the `Err` from the function being compiled. The function has to return
2232/// the same declaration the operand is an instantiation of, so the value it
2233/// hands back is one its caller reads; the operand's success side does not
2234/// have to match, since a `None` or an `Err` carries none of it.
2235fn compile_propagate(
2236    operand: &Expr,
2237    span: Span,
2238    tgt_id: Option<u16>,
2239    v: &mut Vec<Variable>,
2240    ctx: Ctx,
2241    state: &mut State<'_>,
2242    output: &mut Vec<Instr>,
2243) -> u16 {
2244    let operand_type = operand.infer_type(v, ctx, state);
2245    let Some((_, failure_idx)) = type_system::propagation_sides(&operand_type, state) else {
2246        compiler_errors::error_propagate_operand(
2247            &operand_type,
2248            span,
2249            ctx.file_idx,
2250            state.sources,
2251            state.type_names(),
2252        );
2253    };
2254    let function = state.fn_returns.last().cloned();
2255    let fits = ctx.in_function
2256        && function
2257            .as_ref()
2258            .is_some_and(|(_, returns)| propagation_fits(&operand_type, returns, state));
2259    if !fits {
2260        compiler_errors::error_propagate_return(
2261            function
2262                .as_ref()
2263                .filter(|_| ctx.in_function)
2264                .map(|(name, returns)| (name.as_str(), returns)),
2265            &operand_type,
2266            span,
2267            ctx.file_idx,
2268            state.sources,
2269            state.type_names(),
2270        );
2271    }
2272
2273    let operand_reg = operand
2274        .compile(v, ctx, state, output, None, false, true)
2275        .unwrap_id();
2276    // The operand stays rooted while either path reads it.
2277    let v_base = v.len();
2278    v.push(Variable {
2279        declared: None,
2280        name: SmolStr::new_static("[PROPAGATE]"),
2281        register_id: operand_reg,
2282        cell: false,
2283        var_type: operand_type,
2284    });
2285    let tag_reg = state.alloc_reg();
2286    output.push(Instr::GetFieldStruct(operand_reg, 0, tag_reg));
2287    let failure_reg = state.alloc_reg();
2288    output.push(Instr::SetInt(failure_reg, i32::from(failure_idx)));
2289    let to_success = output.len();
2290    output.push(Instr::NotEqJmp(tag_reg, failure_reg, 0));
2291    state.free_reg(failure_reg, v);
2292    state.free_reg(tag_reg, v);
2293
2294    compile_return(
2295        Some(&Expr::Var(SmolStr::new_static("[PROPAGATE]"), span)),
2296        v,
2297        ctx,
2298        state,
2299        output,
2300    );
2301
2302    let success = output.len();
2303    set_jmp_size(&mut output[to_success], (success - to_success) as u16);
2304    let dest = state.alloc_reg_tgt(tgt_id);
2305    output.push(Instr::GetFieldStruct(operand_reg, 1, dest));
2306    v.truncate(v_base);
2307    state.free_reg(operand_reg, v);
2308    dest
2309}
2310
2311/// Whether a function returning `returns` can hand back the `None` or the
2312/// `Err` of an `operand`: both are instantiations of one declaration, and for
2313/// a `Result` the error sides line up.
2314fn propagation_fits(operand: &DataType, returns: &DataType, state: &State<'_>) -> bool {
2315    let (Some((template, operand_args)), Some((returns_template, returns_args))) = (
2316        state.generics.instantiation_of(operand),
2317        state.generics.instantiation_of(returns),
2318    ) else {
2319        return false;
2320    };
2321    template == returns_template
2322        && operand_args
2323            .iter()
2324            .zip(returns_args)
2325            .skip(1)
2326            .all(|(a, b)| param_type_matches(b, a, state.generics))
2327}
2328
2329/// Checks the key of `m[k]` against the key type map `map_type` holds, when it
2330/// names one.
2331fn check_map_key(
2332    map_type: &DataType,
2333    key: &Expr,
2334    span: Span,
2335    v: &mut Vec<Variable>,
2336    ctx: Ctx,
2337    state: &mut State<'_>,
2338) {
2339    let DataType::Map(entry) = map_type else {
2340        return;
2341    };
2342    let Some(expected) = &entry.0 else {
2343        return;
2344    };
2345    let found = key.infer_type(v, ctx, state);
2346    if !param_type_matches(expected, &found, state.generics) {
2347        error_invalid_index_type(
2348            &found,
2349            span,
2350            ctx.file_idx,
2351            state.sources,
2352            state.type_names(),
2353        );
2354    }
2355}
2356
2357/// `m[k]`: the value stored under `k`, raising `unknown_map_key` when the map
2358/// holds no such key.
2359fn compile_map_indexing(
2360    map: &Expr,
2361    map_type: &DataType,
2362    key: &Expr,
2363    span: Span,
2364    v: &mut Vec<Variable>,
2365    ctx: Ctx,
2366    state: &mut State<'_>,
2367    output: &mut Vec<Instr>,
2368) -> u16 {
2369    check_map_key(map_type, key, span, v, ctx, state);
2370    let map_id = map
2371        .compile(v, ctx, state, output, None, false, true)
2372        .unwrap_id();
2373    let key_id = key
2374        .compile(v, ctx, state, output, None, false, true)
2375        .unwrap_id();
2376    state.free_reg(key_id, v);
2377    let dest_reg_id = state.alloc_reg();
2378    output.push(Instr::MapGet(map_id, key_id, dest_reg_id));
2379    state.add_to_src(ctx, output, span);
2380    dest_reg_id
2381}
2382
2383fn compile_array_indexing(
2384    array: &Expr,
2385    index: &Expr,
2386    span: Span,
2387    v: &mut Vec<Variable>,
2388    ctx: Ctx,
2389    state: &mut State<'_>,
2390    output: &mut Vec<Instr>,
2391) -> u16 {
2392    let inferred = array.infer_type(v, ctx, state);
2393    if matches!(inferred, DataType::Map(_)) {
2394        return compile_map_indexing(array, &inferred, index, span, v, ctx, state, output);
2395    }
2396    if !inferred.is_indexable() {
2397        error_type_not_indexable(
2398            &inferred,
2399            span,
2400            false,
2401            ctx.file_idx,
2402            state.sources,
2403            state.type_names(),
2404        );
2405    }
2406
2407    let id = array
2408        .compile(v, ctx, state, output, None, false, true)
2409        .unwrap_id();
2410
2411    let index_inferred = index.infer_type(v, ctx, state);
2412    if index_inferred != DataType::Int {
2413        error_invalid_index_type(
2414            &index_inferred,
2415            span,
2416            ctx.file_idx,
2417            state.sources,
2418            state.type_names(),
2419        );
2420    }
2421    let index_id = index
2422        .compile(v, ctx, state, output, None, false, true)
2423        .unwrap_id();
2424    state.free_reg(index_id, v);
2425    let dest_reg_id = state.alloc_reg();
2426
2427    let to_push = if inferred == DataType::String {
2428        Instr::GetIndexString(id, index_id, dest_reg_id)
2429    } else {
2430        Instr::GetIndexArray(id, index_id, dest_reg_id)
2431    };
2432    output.push(to_push);
2433    state.add_to_src(ctx, output, span);
2434    dest_reg_id
2435}
2436
2437fn compile_array_slice(
2438    array: &Expr,
2439    idx_start: &Expr,
2440    idx_end: Option<&Expr>,
2441    span: Span,
2442    v: &mut Vec<Variable>,
2443    ctx: Ctx,
2444    state: &mut State<'_>,
2445    output: &mut Vec<Instr>,
2446) -> u16 {
2447    let inferred = array.infer_type(v, ctx, state);
2448    if !inferred.is_indexable() {
2449        error_type_not_indexable(
2450            &inferred,
2451            span,
2452            false,
2453            ctx.file_idx,
2454            state.sources,
2455            state.type_names(),
2456        );
2457    }
2458    let id = array
2459        .compile(v, ctx, state, output, None, false, true)
2460        .unwrap_id();
2461    let idx_start_inferred = idx_start.infer_type(v, ctx, state);
2462    if idx_start_inferred != DataType::Int {
2463        error_invalid_index_type(
2464            &idx_start_inferred,
2465            span,
2466            ctx.file_idx,
2467            state.sources,
2468            state.type_names(),
2469        );
2470    }
2471    let idx_start_id = idx_start
2472        .compile(v, ctx, state, output, None, false, true)
2473        .unwrap_id();
2474    let idx_end_id = if let Some(idx_end) = idx_end {
2475        let idx_end_inferred = idx_end.infer_type(v, ctx, state);
2476        if idx_end_inferred != DataType::Int {
2477            error_invalid_index_type(
2478                &idx_end_inferred,
2479                span,
2480                ctx.file_idx,
2481                state.sources,
2482                state.type_names(),
2483            );
2484        }
2485        idx_end
2486            .compile(v, ctx, state, output, None, false, true)
2487            .unwrap_id()
2488    } else {
2489        // `a[i..]` runs to the end, which is the length read when the slice
2490        // is taken.
2491        let len_id = state.alloc_reg();
2492        output.push(Instr::CallLibFunc(LibFunc::Len, id, len_id));
2493        len_id
2494    };
2495    output.push(Instr::StoreFuncArg(idx_end_id));
2496    state.free_reg(idx_start_id, v);
2497    state.free_reg(idx_end_id, v);
2498    let dest_reg_id = state.alloc_reg();
2499    let to_push = if inferred == DataType::String {
2500        Instr::GetSliceString(id, idx_start_id, dest_reg_id)
2501    } else {
2502        Instr::GetSliceArray(id, idx_start_id, dest_reg_id)
2503    };
2504    output.push(to_push);
2505    state.add_to_src(ctx, output, span);
2506    dest_reg_id
2507}
2508
2509#[inline]
2510fn uniform_op2(
2511    instr: fn(u16, u16, u16) -> Instr,
2512    t_1: &'static DataType,
2513    instr2: fn(u16, u16, u16) -> Instr,
2514    t_2: &'static DataType,
2515    symbol: &'static str,
2516    l: &Expr,
2517    r: &Expr,
2518    span_l: Span,
2519    span_r: Span,
2520    tgt_id: Option<u16>,
2521    v: &mut Vec<Variable>,
2522    ctx: Ctx,
2523    state: &mut State<'_>,
2524    output: &mut Vec<Instr>,
2525) -> u16 {
2526    let (t_l, t_r) = (l.infer_type(v, ctx, state), r.infer_type(v, ctx, state));
2527    if !((&t_l == t_1 && &t_r == t_1) || (&t_l == t_2 && &t_r == t_2)) {
2528        if let Some(id) = compile_operator_method(
2529            symbol,
2530            l,
2531            Some(r),
2532            &t_l,
2533            &t_r,
2534            span_l,
2535            span_r,
2536            tgt_id,
2537            v,
2538            ctx,
2539            state,
2540            output,
2541        ) {
2542            return id;
2543        }
2544        compiler_errors::error_op(
2545            &t_l,
2546            &t_r,
2547            symbol,
2548            span_l,
2549            span_r,
2550            ctx.file_idx,
2551            state.sources,
2552            state.type_names(),
2553        );
2554    }
2555    let id_l = l
2556        .compile(v, ctx, state, output, None, false, true)
2557        .unwrap_id();
2558    let id_r = r
2559        .compile(v, ctx, state, output, None, false, true)
2560        .unwrap_id();
2561    state.free_reg(id_l, v);
2562    state.free_reg(id_r, v);
2563    let id = state.alloc_reg_tgt(tgt_id);
2564    output.push(if &t_l == t_1 {
2565        instr(id_l, id_r, id)
2566    } else {
2567        instr2(id_l, id_r, id)
2568    });
2569    id
2570}
2571
2572/// Compiles one of `<`, `<=`, `>`, `>=`.
2573///
2574/// Both operands have to be the same type, and that type has to be one the
2575/// comparison orders: `int`, `float`, or `string`. Strings order by their
2576/// bytes, which is the order `sort` puts a list of strings in.
2577fn compile_cmp_op(
2578    float_instr: fn(u16, u16, u16) -> Instr,
2579    int_instr: fn(u16, u16, u16) -> Instr,
2580    str_instr: fn(u16, u16, u16) -> Instr,
2581    symbol: &'static str,
2582    l: &Expr,
2583    r: &Expr,
2584    span_l: Span,
2585    span_r: Span,
2586    tgt_id: Option<u16>,
2587    v: &mut Vec<Variable>,
2588    ctx: Ctx,
2589    state: &mut State<'_>,
2590    output: &mut Vec<Instr>,
2591) -> u16 {
2592    let (t_l, t_r) = (l.infer_type(v, ctx, state), r.infer_type(v, ctx, state));
2593    if t_l != t_r || !matches!(t_l, DataType::Float | DataType::Int | DataType::String) {
2594        if let Some(id) = compile_operator_method(
2595            symbol,
2596            l,
2597            Some(r),
2598            &t_l,
2599            &t_r,
2600            span_l,
2601            span_r,
2602            tgt_id,
2603            v,
2604            ctx,
2605            state,
2606            output,
2607        ) {
2608            return id;
2609        }
2610        compiler_errors::error_op(
2611            &t_l,
2612            &t_r,
2613            symbol,
2614            span_l,
2615            span_r,
2616            ctx.file_idx,
2617            state.sources,
2618            state.type_names(),
2619        );
2620    }
2621    let id_l = l
2622        .compile(v, ctx, state, output, None, false, true)
2623        .unwrap_id();
2624    let id_r = r
2625        .compile(v, ctx, state, output, None, false, true)
2626        .unwrap_id();
2627    state.free_reg(id_l, v);
2628    state.free_reg(id_r, v);
2629    let id = state.alloc_reg_tgt(tgt_id);
2630    output.push(if t_l == DataType::String {
2631        str_instr(id_l, id_r, id)
2632    } else if t_l == DataType::Float {
2633        float_instr(id_l, id_r, id)
2634    } else {
2635        int_instr(id_l, id_r, id)
2636    });
2637    id
2638}
2639
2640fn compile_div_op(
2641    l: &Expr,
2642    r: &Expr,
2643    span_l: Span,
2644    span_r: Span,
2645    tgt_id: Option<u16>,
2646    v: &mut Vec<Variable>,
2647    ctx: Ctx,
2648    state: &mut State<'_>,
2649    output: &mut Vec<Instr>,
2650) -> u16 {
2651    // A float left operand next to an `int` zero is a type error, and naming it
2652    // division by zero would report the wrong mistake. Every other left operand
2653    // makes this integer division, which does not divide by zero.
2654    if let Expr::Int(n) = r
2655        && *n == 0
2656        && let t_l = l.infer_type(v, ctx, state)
2657        && t_l != DataType::Float
2658        && !matches!(t_l, DataType::Struct(_) | DataType::Enum(_))
2659    {
2660        error_division_by_zero(false, span_l.extend(span_r), ctx.file_idx, state.sources);
2661    }
2662    let id = uniform_op2(
2663        Instr::DivFloat,
2664        &DataType::Float,
2665        Instr::DivInt,
2666        &DataType::Int,
2667        "/",
2668        l,
2669        r,
2670        span_l,
2671        span_r,
2672        tgt_id,
2673        v,
2674        ctx,
2675        state,
2676        output,
2677    );
2678    if matches!(output.last(), Some(Instr::DivInt(..))) {
2679        state.add_to_src(ctx, output, span_l.extend(span_r));
2680    }
2681    id
2682}
2683
2684fn compile_add_op(
2685    l: &Expr,
2686    r: &Expr,
2687    span_l: Span,
2688    span_r: Span,
2689    tgt_id: Option<u16>,
2690    v: &mut Vec<Variable>,
2691    ctx: Ctx,
2692    state: &mut State<'_>,
2693    output: &mut Vec<Instr>,
2694) -> u16 {
2695    let t_l = l.infer_type(v, ctx, state);
2696    let t_r = r.infer_type(v, ctx, state);
2697    if t_l != t_r
2698        || !matches!(
2699            t_l,
2700            DataType::String | DataType::Array(_) | DataType::Float | DataType::Int
2701        )
2702    {
2703        if let Some(id) = compile_operator_method(
2704            "+",
2705            l,
2706            Some(r),
2707            &t_l,
2708            &t_r,
2709            span_l,
2710            span_r,
2711            tgt_id,
2712            v,
2713            ctx,
2714            state,
2715            output,
2716        ) {
2717            return id;
2718        }
2719        compiler_errors::error_op(
2720            &t_l,
2721            &t_r,
2722            "+",
2723            span_l,
2724            span_r,
2725            ctx.file_idx,
2726            state.sources,
2727            state.type_names(),
2728        );
2729    }
2730    // var+1 or 1+var use the dedicated IncInt/IncIntTo instructions
2731    if t_l == DataType::Int
2732        && let Some(Expr::Var(src_name, _)) = {
2733            if matches!(r, Expr::Int(1)) {
2734                Some(l)
2735            } else if matches!(l, Expr::Int(1)) {
2736                Some(r)
2737            } else {
2738                None
2739            }
2740        }
2741        && let Some(src_var) = v.iter().rfind(|x| x.name == *src_name)
2742        && !src_var.cell
2743    {
2744        let src_id = src_var.register_id;
2745        let id = tgt_id.unwrap_or_else(|| state.alloc_reg());
2746        output.push(if src_id == id {
2747            Instr::IncInt(id)
2748        } else {
2749            Instr::IncIntTo(src_id, id)
2750        });
2751        return id;
2752    }
2753    let id_l = l
2754        .compile(v, ctx, state, output, None, false, true)
2755        .unwrap_id();
2756    let id_r = r
2757        .compile(v, ctx, state, output, None, false, true)
2758        .unwrap_id();
2759    state.free_reg(id_l, v);
2760    state.free_reg(id_r, v);
2761    let id = state.alloc_reg_tgt(tgt_id);
2762    if matches!(t_l, DataType::Array(_)) {
2763        output.push(Instr::AddArray(id_l, id_r, id));
2764    } else if t_l == DataType::String {
2765        output.push(Instr::AddStr(id_l, id_r, id));
2766    } else if t_l == DataType::Float {
2767        output.push(Instr::AddFloat(id_l, id_r, id));
2768    } else {
2769        output.push(Instr::AddInt(id_l, id_r, id));
2770    }
2771    id
2772}
2773
2774fn compile_sub_op(
2775    l: &Expr,
2776    r: &Expr,
2777    span_l: Span,
2778    span_r: Span,
2779    tgt_id: Option<u16>,
2780    v: &mut Vec<Variable>,
2781    ctx: Ctx,
2782    state: &mut State<'_>,
2783    output: &mut Vec<Instr>,
2784) -> u16 {
2785    let t_l = l.infer_type(v, ctx, state);
2786    let t_r = r.infer_type(v, ctx, state);
2787    if !((t_l == DataType::Float && t_r == DataType::Float)
2788        || (t_l == DataType::Int && t_r == DataType::Int))
2789    {
2790        if let Some(id) = compile_operator_method(
2791            "-",
2792            l,
2793            Some(r),
2794            &t_l,
2795            &t_r,
2796            span_l,
2797            span_r,
2798            tgt_id,
2799            v,
2800            ctx,
2801            state,
2802            output,
2803        ) {
2804            return id;
2805        }
2806        compiler_errors::error_op(
2807            &t_l,
2808            &t_r,
2809            "-",
2810            span_l,
2811            span_r,
2812            ctx.file_idx,
2813            state.sources,
2814            state.type_names(),
2815        );
2816    }
2817    // var-1 uses the dedicated DecInt/DecIntTo instructions
2818    if t_l == DataType::Int
2819        && matches!(r, Expr::Int(1))
2820        && let Expr::Var(src_name, _) = l
2821        && let Some(src_var) = v.iter().rfind(|x| x.name == *src_name)
2822        && !src_var.cell
2823    {
2824        let src_id = src_var.register_id;
2825        let id = tgt_id.unwrap_or_else(|| state.alloc_reg());
2826        output.push(if src_id == id {
2827            Instr::DecInt(id)
2828        } else {
2829            Instr::DecIntTo(src_id, id)
2830        });
2831        return id;
2832    }
2833    let id_l = l
2834        .compile(v, ctx, state, output, None, false, true)
2835        .unwrap_id();
2836    let id_r = r
2837        .compile(v, ctx, state, output, None, false, true)
2838        .unwrap_id();
2839    state.free_reg(id_l, v);
2840    state.free_reg(id_r, v);
2841    let id = state.alloc_reg_tgt(tgt_id);
2842    output.push(if t_l == DataType::Float {
2843        Instr::SubFloat(id_l, id_r, id)
2844    } else {
2845        Instr::SubInt(id_l, id_r, id)
2846    });
2847    id
2848}
2849
2850fn compile_mod_op(
2851    l: &Expr,
2852    r: &Expr,
2853    span_l: Span,
2854    span_r: Span,
2855    tgt_id: Option<u16>,
2856    v: &mut Vec<Variable>,
2857    ctx: Ctx,
2858    state: &mut State<'_>,
2859    output: &mut Vec<Instr>,
2860) -> u16 {
2861    // As in `compile_div_op`: a float left operand makes this a type error
2862    // rather than a remainder by zero.
2863    if let Expr::Int(n) = r
2864        && *n == 0
2865        && let t_l = l.infer_type(v, ctx, state)
2866        && t_l != DataType::Float
2867        && !matches!(t_l, DataType::Struct(_) | DataType::Enum(_))
2868    {
2869        error_division_by_zero(true, span_l.extend(span_r), ctx.file_idx, state.sources);
2870    }
2871    let id = uniform_op2(
2872        Instr::ModFloat,
2873        &DataType::Float,
2874        Instr::ModInt,
2875        &DataType::Int,
2876        "%",
2877        l,
2878        r,
2879        span_l,
2880        span_r,
2881        tgt_id,
2882        v,
2883        ctx,
2884        state,
2885        output,
2886    );
2887    if matches!(output.last(), Some(Instr::ModInt(..))) {
2888        state.add_to_src(ctx, output, span_l.extend(span_r));
2889    }
2890    id
2891}
2892
2893/// Compiles `&&` or `||` where a value is wanted rather than a branch.
2894///
2895/// The left operand is evaluated into the result register and, when it already
2896/// settles the answer, the jump skips the right operand entirely, so an
2897/// expression short-circuits wherever it appears and not only as the condition
2898/// of an `if` or a `while`.
2899///
2900/// The right operand is evaluated into its own register and moved, which keeps
2901/// the last instruction a `Mov`. An enclosing condition fuses the instruction it
2902/// finds at the end of a compiled condition into a jump, and fusing the right
2903/// operand's comparison would strand the short-circuit jump past it.
2904fn compile_short_circuit_value(
2905    l: &Expr,
2906    r: &Expr,
2907    span_l: Span,
2908    span_r: Span,
2909    symbol: &'static str,
2910    tgt_id: Option<u16>,
2911    v: &mut Vec<Variable>,
2912    ctx: Ctx,
2913    state: &mut State<'_>,
2914    output: &mut Vec<Instr>,
2915    keep_facts: bool,
2916) -> u16 {
2917    let and = symbol == "&&";
2918    let v_len = v.len();
2919    let t_l = l.infer_type(v, ctx, state);
2920    if and {
2921        type_test::declare_condition_facts(l, v, ctx, state);
2922    }
2923    let t_r = r.infer_type(v, ctx, state);
2924    v.truncate(v_len);
2925    if t_l != DataType::Bool || t_r != DataType::Bool {
2926        cold_path();
2927        compiler_errors::error_op(
2928            &t_l,
2929            &t_r,
2930            symbol,
2931            span_l,
2932            span_r,
2933            ctx.file_idx,
2934            state.sources,
2935            state.type_names(),
2936        );
2937    }
2938
2939    let id = state.alloc_reg_tgt(tgt_id);
2940    // The right of an `&&` runs only where the left held, so what an `is` on
2941    // the left proved is in scope for it, and its payload is read once the
2942    // jump has let it through.
2943    let (left_id, facts) = match l {
2944        Expr::Is(operand, target, operand_span, target_span) if and => {
2945            let outer = std::mem::replace(
2946                &mut state.captured_binders,
2947                type_test::captured_binders(l, std::slice::from_ref(r)),
2948            );
2949            let compiled = type_test::compile_is(
2950                operand,
2951                target,
2952                *operand_span,
2953                *target_span,
2954                Some(id),
2955                true,
2956                v,
2957                ctx,
2958                state,
2959                output,
2960            );
2961            state.captured_binders = outer;
2962            (compiled.0, Some(compiled.1))
2963        }
2964        // A chain of `&&` keeps what its operands proved for the operand
2965        // after it.
2966        Expr::BoolAnd(ll, lr, span_ll, span_lr) if and => (
2967            compile_short_circuit_value(
2968                ll,
2969                lr,
2970                *span_ll,
2971                *span_lr,
2972                "&&",
2973                Some(id),
2974                v,
2975                ctx,
2976                state,
2977                output,
2978                true,
2979            ),
2980            None,
2981        ),
2982        _ => (
2983            l.compile(v, ctx, state, output, Some(id), false, true)
2984                .unwrap_id(),
2985            None,
2986        ),
2987    };
2988    if left_id != id {
2989        output.push(Instr::Mov(left_id, id));
2990    }
2991
2992    let skip_idx = output.len();
2993    // `&&` is settled by a false left operand, `||` by a true one. Either way
2994    // the left operand's value is already in the result register.
2995    output.push(if and {
2996        Instr::IsFalseJmp(id, 0)
2997    } else {
2998        Instr::IsTrueJmp(id, 0)
2999    });
3000    if let Some(facts) = facts {
3001        facts.apply(v, state, output);
3002    }
3003
3004    let right_id = r
3005        .compile(v, ctx, state, output, None, false, true)
3006        .unwrap_id();
3007    if !keep_facts {
3008        v.truncate(v_len);
3009    }
3010    state.free_reg(right_id, v);
3011    output.push(Instr::Mov(right_id, id));
3012    let skip_size = (output.len() - skip_idx) as u16;
3013    set_jmp_size(&mut output[skip_idx], skip_size);
3014    id
3015}
3016
3017/// Whether the two sides of an equality compare by contents.
3018///
3019/// A list, a map, a struct and an enum are handles into a pool, so comparing
3020/// the registers answers whether the two sides are the same object rather than
3021/// whether they hold the same thing. `ObjEq` walks the contents instead.
3022const fn compares_by_contents(l_type: &DataType, r_type: &DataType) -> bool {
3023    const fn is_object(t: &DataType) -> bool {
3024        matches!(
3025            t,
3026            DataType::Array(_) | DataType::Map(_) | DataType::Struct(_) | DataType::Enum(_)
3027        )
3028    }
3029    is_object(l_type) && is_object(r_type)
3030}
3031
3032fn compile_eq_op(
3033    l: &Expr,
3034    r: &Expr,
3035    span_l: Span,
3036    span_r: Span,
3037    tgt_id: Option<u16>,
3038    v: &mut Vec<Variable>,
3039    ctx: Ctx,
3040    state: &mut State<'_>,
3041    output: &mut Vec<Instr>,
3042) -> u16 {
3043    let l_type = l.infer_type(v, ctx, state);
3044    let r_type = r.infer_type(v, ctx, state);
3045    // A type that defines `==` answers for its own values; without one, two
3046    // structs still compare field by field, which is what they always did.
3047    if let Some(id) = compile_operator_method(
3048        "==",
3049        l,
3050        Some(r),
3051        &l_type,
3052        &r_type,
3053        span_l,
3054        span_r,
3055        tgt_id,
3056        v,
3057        ctx,
3058        state,
3059        output,
3060    ) {
3061        return id;
3062    }
3063    let by_contents = compares_by_contents(&l_type, &r_type);
3064    let is_string = l_type == DataType::String || r_type == DataType::String;
3065    let id_l = l
3066        .compile(v, ctx, state, output, None, false, true)
3067        .unwrap_id();
3068    let id_r = r
3069        .compile(v, ctx, state, output, None, false, true)
3070        .unwrap_id();
3071    state.free_reg(id_l, v);
3072    state.free_reg(id_r, v);
3073    let id = state.alloc_reg_tgt(tgt_id);
3074    output.push(if by_contents {
3075        Instr::ObjEq(id_l, id_r, id)
3076    } else if is_string {
3077        Instr::StrEq(id_l, id_r, id)
3078    } else {
3079        Instr::Eq(id_l, id_r, id)
3080    });
3081    id
3082}
3083
3084fn compile_neq_op(
3085    l: &Expr,
3086    r: &Expr,
3087    span_l: Span,
3088    span_r: Span,
3089    tgt_id: Option<u16>,
3090    v: &mut Vec<Variable>,
3091    ctx: Ctx,
3092    state: &mut State<'_>,
3093    output: &mut Vec<Instr>,
3094) -> u16 {
3095    let l_type = l.infer_type(v, ctx, state);
3096    let r_type = r.infer_type(v, ctx, state);
3097    // `!=` has no method of its own: it is the type's `==` with the answer
3098    // flipped.
3099    if let Some(id) = compile_operator_method(
3100        "!=",
3101        l,
3102        Some(r),
3103        &l_type,
3104        &r_type,
3105        span_l,
3106        span_r,
3107        tgt_id,
3108        v,
3109        ctx,
3110        state,
3111        output,
3112    ) {
3113        return id;
3114    }
3115    let by_contents = compares_by_contents(&l_type, &r_type);
3116    let is_string = l_type == DataType::String || r_type == DataType::String;
3117    let id_l = l
3118        .compile(v, ctx, state, output, None, false, true)
3119        .unwrap_id();
3120    let id_r = r
3121        .compile(v, ctx, state, output, None, false, true)
3122        .unwrap_id();
3123    state.free_reg(id_l, v);
3124    state.free_reg(id_r, v);
3125    let id = state.alloc_reg_tgt(tgt_id);
3126    if by_contents {
3127        output.push(Instr::ObjNotEq(id_l, id_r, id));
3128    } else if is_string {
3129        output.push(Instr::StrNotEq(id_l, id_r, id));
3130    } else {
3131        output.push(Instr::NotEq(id_l, id_r, id));
3132    }
3133    id
3134}
3135
3136fn compile_neg_op(
3137    l: &Expr,
3138    span_l: Span,
3139    span_r: Span,
3140    tgt_id: Option<u16>,
3141    v: &mut Vec<Variable>,
3142    ctx: Ctx,
3143    state: &mut State<'_>,
3144    output: &mut Vec<Instr>,
3145) -> u16 {
3146    let operand_type = l.infer_type(v, ctx, state);
3147    if let Some(id) = compile_operator_method(
3148        UNARY_MINUS_METHOD,
3149        l,
3150        None,
3151        &operand_type,
3152        &operand_type,
3153        span_l,
3154        span_r,
3155        tgt_id,
3156        v,
3157        ctx,
3158        state,
3159        output,
3160    ) {
3161        return id;
3162    }
3163    let id_l = l
3164        .compile(v, ctx, state, output, None, false, true)
3165        .unwrap_id();
3166    state.free_reg(id_l, v);
3167    let id = state.alloc_reg_tgt(tgt_id);
3168    if operand_type == DataType::Float {
3169        output.push(Instr::NegFloat(id_l, id));
3170    } else if operand_type == DataType::Int {
3171        output.push(Instr::NegInt(id_l, id));
3172    } else {
3173        compiler_errors::error_op(
3174            &DataType::Null,
3175            &operand_type,
3176            "-",
3177            span_l,
3178            span_r,
3179            ctx.file_idx,
3180            state.sources,
3181            state.type_names(),
3182        );
3183    }
3184    id
3185}
3186
3187/// Compiles an operator whose operands are both `int` and nothing else: the
3188/// bitwise operators and the shifts.
3189///
3190/// [`uniform_op2`] covers the operators that take a pair of `int` or a pair of
3191/// `float`; this one has a single accepted pair, so a `float` operand reaches
3192/// the same report a `string` one does.
3193#[allow(clippy::too_many_arguments)]
3194fn int_op2(
3195    instr: fn(u16, u16, u16) -> Instr,
3196    symbol: &'static str,
3197    l: &Expr,
3198    r: &Expr,
3199    span_l: Span,
3200    span_r: Span,
3201    tgt_id: Option<u16>,
3202    v: &mut Vec<Variable>,
3203    ctx: Ctx,
3204    state: &mut State<'_>,
3205    output: &mut Vec<Instr>,
3206) -> u16 {
3207    let (t_l, t_r) = (l.infer_type(v, ctx, state), r.infer_type(v, ctx, state));
3208    if t_l != DataType::Int || t_r != DataType::Int {
3209        if let Some(id) = compile_operator_method(
3210            symbol,
3211            l,
3212            Some(r),
3213            &t_l,
3214            &t_r,
3215            span_l,
3216            span_r,
3217            tgt_id,
3218            v,
3219            ctx,
3220            state,
3221            output,
3222        ) {
3223            return id;
3224        }
3225        compiler_errors::error_op(
3226            &t_l,
3227            &t_r,
3228            symbol,
3229            span_l,
3230            span_r,
3231            ctx.file_idx,
3232            state.sources,
3233            state.type_names(),
3234        );
3235    }
3236    let id_l = l
3237        .compile(v, ctx, state, output, None, false, true)
3238        .unwrap_id();
3239    let id_r = r
3240        .compile(v, ctx, state, output, None, false, true)
3241        .unwrap_id();
3242    state.free_reg(id_l, v);
3243    state.free_reg(id_r, v);
3244    let id = state.alloc_reg_tgt(tgt_id);
3245    output.push(instr(id_l, id_r, id));
3246    id
3247}
3248
3249/// Compiles `<<` or `>>`, refusing a count written as a literal that `int` has
3250/// no room for. A count only known while the program runs is checked by the
3251/// instruction instead, which raises `shift_count_out_of_range`.
3252#[allow(clippy::too_many_arguments)]
3253fn compile_shift_op(
3254    instr: fn(u16, u16, u16) -> Instr,
3255    symbol: &'static str,
3256    l: &Expr,
3257    r: &Expr,
3258    span_l: Span,
3259    span_r: Span,
3260    tgt_id: Option<u16>,
3261    v: &mut Vec<Variable>,
3262    ctx: Ctx,
3263    state: &mut State<'_>,
3264    output: &mut Vec<Instr>,
3265) -> u16 {
3266    if let Expr::Int(count) = r
3267        && !shift_count_in_range(*count)
3268        && !matches!(
3269            l.infer_type(v, ctx, state),
3270            DataType::Struct(_) | DataType::Enum(_)
3271        )
3272    {
3273        error_shift_count_out_of_range(*count, span_l.extend(span_r), ctx.file_idx, state.sources);
3274    }
3275    int_op2(
3276        instr, symbol, l, r, span_l, span_r, tgt_id, v, ctx, state, output,
3277    )
3278}
3279
3280/// Compiles `~`, which flips the bits of an `int`.
3281fn compile_bit_not_op(
3282    l: &Expr,
3283    span_l: Span,
3284    span_r: Span,
3285    tgt_id: Option<u16>,
3286    v: &mut Vec<Variable>,
3287    ctx: Ctx,
3288    state: &mut State<'_>,
3289    output: &mut Vec<Instr>,
3290) -> u16 {
3291    let operand_type = l.infer_type(v, ctx, state);
3292    if let Some(id) = compile_operator_method(
3293        "~",
3294        l,
3295        None,
3296        &operand_type,
3297        &operand_type,
3298        span_l,
3299        span_r,
3300        tgt_id,
3301        v,
3302        ctx,
3303        state,
3304        output,
3305    ) {
3306        return id;
3307    }
3308    let id_l = l
3309        .compile(v, ctx, state, output, None, false, true)
3310        .unwrap_id();
3311    state.free_reg(id_l, v);
3312    let id = state.alloc_reg_tgt(tgt_id);
3313    if operand_type != DataType::Int {
3314        compiler_errors::error_op(
3315            &DataType::Null,
3316            &operand_type,
3317            "~",
3318            span_l,
3319            span_r,
3320            ctx.file_idx,
3321            state.sources,
3322            state.type_names(),
3323        );
3324    }
3325    output.push(Instr::BitNotInt(id_l, id));
3326    id
3327}
3328
3329fn compile_bool_neg_op(
3330    l: &Expr,
3331    span_l: Span,
3332    span_r: Span,
3333    tgt_id: Option<u16>,
3334    v: &mut Vec<Variable>,
3335    ctx: Ctx,
3336    state: &mut State<'_>,
3337    output: &mut Vec<Instr>,
3338) -> u16 {
3339    let operand_type = l.infer_type(v, ctx, state);
3340    let id_l = l
3341        .compile(v, ctx, state, output, None, false, true)
3342        .unwrap_id();
3343    state.free_reg(id_l, v);
3344    let id = state.alloc_reg_tgt(tgt_id);
3345    if operand_type != DataType::Bool {
3346        compiler_errors::error_op(
3347            &DataType::Null,
3348            &operand_type,
3349            "!",
3350            span_l,
3351            span_r,
3352            ctx.file_idx,
3353            state.sources,
3354            state.type_names(),
3355        );
3356    }
3357    output.push(Instr::NegBool(id_l, id));
3358    id
3359}
3360
3361/// Compiles one branch of a construct used as a value, an `if` or a `match`,
3362/// into `tgt_id`: its statements, then its last expression as the value.
3363///
3364/// `as_fn_value` says the construct's branches give different functions, so
3365/// the value is a function value that a call dispatches on rather than one
3366/// function the compiler names; see [`merge_fn_types`].
3367fn compile_inline_condition_branch(
3368    branch: &[Expr],
3369    v: &mut Vec<Variable>,
3370    ctx: Ctx,
3371    state: &mut State<'_>,
3372    output: &mut Vec<Instr>,
3373    tgt_id: u16,
3374    as_fn_value: bool,
3375) {
3376    let regs_before = state.registers.len() as u16;
3377    let output_len = output.len();
3378    output.extend(compile_expr(
3379        &branch[..branch.len() - 1],
3380        v,
3381        ctx.advance_offset(output.len() as u16),
3382        state,
3383    ));
3384    // The value compiles onto the end of `output` itself, whose start `ctx`
3385    // already places, so its offset is not advanced the way the statements'
3386    // separate buffer is.
3387    let last = &branch[branch.len() - 1];
3388    let val_id = if as_fn_value {
3389        compile_fn_value(last, v, ctx, state, output, Some(tgt_id))
3390    } else {
3391        last.compile(v, ctx, state, output, Some(tgt_id), false, true)
3392            .unwrap_id()
3393    };
3394    state.free_scope_registers(regs_before, &output[output_len..], v);
3395    if val_id != tgt_id {
3396        output.push(Instr::Mov(val_id, tgt_id));
3397    }
3398}
3399
3400fn compile_inline_condition(
3401    main_condition: &Expr,
3402    code: &[Expr],
3403    span: Span,
3404    condition_span: Span,
3405    v: &mut Vec<Variable>,
3406    ctx: Ctx,
3407    state: &mut State<'_>,
3408    output: &mut Vec<Instr>,
3409    tgt_id: Option<u16>,
3410    as_fn_value: bool,
3411) -> u16 {
3412    let return_id = state.alloc_reg_tgt(tgt_id);
3413
3414    // get first code limit (after which there are only else(if) blocks)
3415    let main_code_limit = code
3416        .iter()
3417        .position(|x| matches!(x, Expr::ElseIfBlock(_, _, _) | Expr::ElseBlock(_)))
3418        .unwrap_or(code.len());
3419
3420    let condition_blocks_count = code.len() - main_code_limit;
3421    // One entry per branch with a condition: the jumps it takes when false.
3422    let mut false_jumps: Vec<Vec<usize>> = Vec::with_capacity(condition_blocks_count + 1);
3423    let mut jmp_markers: Vec<usize> = Vec::with_capacity(condition_blocks_count);
3424    let mut condition_markers: Vec<usize> = Vec::with_capacity(condition_blocks_count);
3425
3426    // parse the main condition; the branch sees what it proved
3427    let v_len = v.len();
3428    false_jumps.push(compile_guard(
3429        main_condition,
3430        condition_span,
3431        &code[..main_code_limit],
3432        v,
3433        ctx,
3434        state,
3435        output,
3436    ));
3437
3438    compile_inline_condition_branch(
3439        &code[..main_code_limit],
3440        v,
3441        ctx,
3442        state,
3443        output,
3444        return_id,
3445        as_fn_value,
3446    );
3447    v.truncate(v_len);
3448    if main_code_limit != code.len() {
3449        output.push(Instr::Jmp(0));
3450        jmp_markers.push(output.len() - 1);
3451    }
3452
3453    let mut else_exists = false;
3454    for elem in &code[main_code_limit..] {
3455        if let Expr::ElseIfBlock(condition, code, condition_span) = elem {
3456            condition_markers.push(output.len());
3457            false_jumps.push(compile_guard(
3458                condition,
3459                *condition_span,
3460                code,
3461                v,
3462                ctx,
3463                state,
3464                output,
3465            ));
3466            compile_inline_condition_branch(code, v, ctx, state, output, return_id, as_fn_value);
3467            v.truncate(v_len);
3468            output.push(Instr::Jmp(0));
3469            jmp_markers.push(output.len() - 1);
3470        } else if let Expr::ElseBlock(code) = elem {
3471            else_exists = true;
3472            condition_markers.push(output.len());
3473            compile_inline_condition_branch(code, v, ctx, state, output, return_id, as_fn_value);
3474        }
3475    }
3476    if !else_exists {
3477        error_conditional_expression_without_else(span, ctx.file_idx, state.sources);
3478    }
3479
3480    for y in jmp_markers {
3481        let diff = output.len() - y;
3482        output[y] = Instr::Jmp(diff as u16);
3483    }
3484    // A false condition goes on to the next branch, which is always there:
3485    // the `else` closes the chain.
3486    for (i, jumps) in false_jumps.iter().enumerate() {
3487        let target = condition_markers[i];
3488        for &y in jumps {
3489            set_jmp_size(&mut output[y], (target - y) as u16);
3490        }
3491    }
3492    return_id
3493}
3494
3495fn compile_array_index_assignment(
3496    array: &Expr,
3497    index: &Expr,
3498    value: &Expr,
3499    index_span: Span,
3500    elem_span: Span,
3501    v: &mut Vec<Variable>,
3502    ctx: Ctx,
3503    state: &mut State<'_>,
3504    output: &mut Vec<Instr>,
3505) {
3506    let array_type = array.infer_type(v, ctx, state);
3507    if matches!(array_type, DataType::Map(_)) {
3508        compile_map_entry_assignment(
3509            array,
3510            &array_type,
3511            index,
3512            value,
3513            index_span,
3514            elem_span,
3515            v,
3516            ctx,
3517            state,
3518            output,
3519        );
3520        return;
3521    }
3522    if !array_type.is_indexable() {
3523        error_type_not_indexable(
3524            &array_type,
3525            index_span,
3526            false,
3527            ctx.file_idx,
3528            state.sources,
3529            state.type_names(),
3530        );
3531    }
3532    // Get the id of the source array/string (may be a nested GetIndex)
3533    let id = array
3534        .compile(v, ctx, state, output, None, false, true)
3535        .unwrap_id();
3536
3537    let final_id = index
3538        .compile(v, ctx, state, output, None, false, true)
3539        .unwrap_id();
3540
3541    let elem_type = value.infer_type(v, ctx, state);
3542    let elem_id = value
3543        .compile(v, ctx, state, output, None, false, true)
3544        .unwrap_id();
3545    state.free_reg(elem_id, v);
3546    // An element takes the same rule a map entry and a `push` argument do: an
3547    // `any` slot, such as the elements of an `as_list` list, holds any value.
3548    if matches!(&array_type, DataType::Array(Some(held))
3549            if !param_type_matches(held, &elem_type, state.generics))
3550        || (array_type == DataType::String && elem_type != DataType::String)
3551    {
3552        error_cannot_push_type_to_array(
3553            &array_type,
3554            &elem_type,
3555            index_span,
3556            elem_span,
3557            ctx.file_idx,
3558            state.sources,
3559            state.type_names(),
3560        );
3561    }
3562
3563    let to_push = if array_type == DataType::String {
3564        Instr::SetElementString(id, elem_id, final_id)
3565    } else {
3566        Instr::SetElementObj(id, elem_id, final_id)
3567    };
3568    output.push(to_push);
3569    state.add_to_src(ctx, output, index_span);
3570    state.free_reg(id, v);
3571}
3572
3573/// `m[k] = v`: stores `v` under `k`, adding the entry or replacing the value
3574/// already there. An empty map literal takes its key and value types from the
3575/// first entry written, the way an empty list takes its element type from the
3576/// first `push`.
3577fn compile_map_entry_assignment(
3578    map: &Expr,
3579    map_type: &DataType,
3580    key: &Expr,
3581    value: &Expr,
3582    key_span: Span,
3583    value_span: Span,
3584    v: &mut Vec<Variable>,
3585    ctx: Ctx,
3586    state: &mut State<'_>,
3587    output: &mut Vec<Instr>,
3588) {
3589    check_map_key(map_type, key, key_span, v, ctx, state);
3590    let value_type = value.infer_type(v, ctx, state);
3591    if let DataType::Map(entry) = map_type
3592        && let Some(expected) = &entry.1
3593        && !param_type_matches(expected, &value_type, state.generics)
3594    {
3595        error_invalid_type(
3596            expected,
3597            &value_type,
3598            value_span,
3599            None,
3600            Some(format_args!(
3601                "The map holds {} values",
3602                state.type_names().of(expected)
3603            )),
3604            ctx.file_idx,
3605            state.sources,
3606            state.type_names(),
3607        );
3608    }
3609    if matches!(map_type, DataType::Map(m) if m.0.is_none() && m.1.is_none()) {
3610        let key_type = key.infer_type(v, ctx, state);
3611        pin_inserted_entry(map, &key_type, &value_type, v);
3612    }
3613    let map_id = map
3614        .compile(v, ctx, state, output, None, false, true)
3615        .unwrap_id();
3616    let key_id = key
3617        .compile(v, ctx, state, output, None, false, true)
3618        .unwrap_id();
3619    let value_id = value
3620        .compile(v, ctx, state, output, None, false, true)
3621        .unwrap_id();
3622    state.free_reg(key_id, v);
3623    state.free_reg(value_id, v);
3624    output.push(Instr::MapInsertReg(map_id, key_id, value_id));
3625    state.free_reg(map_id, v);
3626}
3627
3628fn compile_struct_field_assignment(
3629    struct_expr: &Expr,
3630    field: &SmolStr,
3631    new_val: &Expr,
3632    struct_span: Span,
3633    field_span: Span,
3634    value_span: Span,
3635    v: &mut Vec<Variable>,
3636    ctx: Ctx,
3637    state: &mut State<'_>,
3638    output: &mut Vec<Instr>,
3639) {
3640    let t = struct_expr.infer_type(v, ctx, state);
3641    let new_val_type = new_val.infer_type(v, ctx, state);
3642    let DataType::Struct(struct_id) = t else {
3643        error_invalid_type(
3644            &DataType::Struct(0),
3645            &t,
3646            struct_span,
3647            None,
3648            None,
3649            ctx.file_idx,
3650            state.sources,
3651            state.type_names(),
3652        );
3653    };
3654    let field_struct = &state.structs[struct_id as usize];
3655    let Some(field_index) = field_struct
3656        .fields
3657        .iter()
3658        .position(|(expected_field_name, _, _)| expected_field_name == field)
3659    else {
3660        compiler_errors::error_struct_unknown_field(
3661            ctx.file_idx,
3662            field_span,
3663            field,
3664            &field_struct.name,
3665            &field_struct.fields,
3666            state.sources,
3667        );
3668    };
3669    let struct_name = field_struct.name.clone();
3670    // The value is checked against the field the way a struct literal checks
3671    // it, so a field that holds a function takes what the literal would.
3672    let as_fn_value = compile_struct_field_type(
3673        &struct_name,
3674        struct_id as usize,
3675        field_index,
3676        new_val,
3677        value_span,
3678        v,
3679        ctx,
3680        state,
3681        output,
3682    );
3683    let field_index = field_index as u16;
3684    let id = struct_expr
3685        .compile(v, ctx, state, output, None, false, true)
3686        .unwrap_id();
3687    // `s.f += x` on a float field reads, adds and writes the field in one
3688    // instruction. The field is read after `x` is worked out rather than
3689    // before, which only a call inside `x` could tell apart, so `x` may hold
3690    // none.
3691    if let Expr::Add(read, added, _, _) = new_val
3692        && let (Expr::GetStructField(read_struct, read_field, _, _), Expr::Var(name, _)) =
3693            (read.as_ref(), struct_expr)
3694        && read_field == field
3695        && matches!(read_struct.as_ref(), Expr::Var(read_name, _) if read_name == name)
3696        && state.structs[struct_id as usize].fields[field_index as usize].1 == DataType::Float
3697        && new_val_type == DataType::Float
3698        && calls_nothing(added, v, ctx, state)
3699    {
3700        let added_id = added
3701            .compile(v, ctx, state, output, None, false, true)
3702            .unwrap_id();
3703        state.free_reg(added_id, v);
3704        output.push(Instr::AddFieldFloat(id, added_id, field_index));
3705        return;
3706    }
3707    let new_elem_reg_id = compile_element(new_val, as_fn_value, v, ctx, state, output);
3708    output.push(Instr::SetFieldStruct(id, new_elem_reg_id, field_index));
3709}
3710
3711/// Whether `expr` is arithmetic on numbers, names, literals and field reads
3712/// alone, so working it out runs no candela code and writes nothing.
3713fn calls_nothing(expr: &Expr, v: &mut Vec<Variable>, ctx: Ctx, state: &mut State<'_>) -> bool {
3714    match expr {
3715        Expr::Var(..) | Expr::Float(_) | Expr::Int(_) => true,
3716        Expr::GetStructField(inner, _, _, _) => calls_nothing(inner, v, ctx, state),
3717        Expr::Add(l, r, _, _)
3718        | Expr::Sub(l, r, _, _)
3719        | Expr::Mul(l, r, _, _)
3720        | Expr::Div(l, r, _, _) => {
3721            // An operator on anything but numbers can be a method written in
3722            // candela.
3723            [l, r].into_iter().all(|side| {
3724                matches!(
3725                    side.infer_type(v, ctx, state),
3726                    DataType::Float | DataType::Int
3727                ) && calls_nothing(side, v, ctx, state)
3728            })
3729        }
3730        _ => false,
3731    }
3732}
3733
3734/// Compiles the condition of an `if`, an `else if` or a `while`, leaving what
3735/// it proved in scope for `body`, the code it guards: the variables an `is`
3736/// narrowed and the names a variant test bound. Answers the jumps taken when
3737/// the condition is false, for the caller to aim past the body; the caller
3738/// also takes the condition's names out of scope once the body is compiled.
3739#[allow(clippy::too_many_arguments)]
3740fn compile_guard(
3741    condition: &Expr,
3742    condition_span: Span,
3743    body: &[Expr],
3744    v: &mut Vec<Variable>,
3745    ctx: Ctx,
3746    state: &mut State<'_>,
3747    output: &mut Vec<Instr>,
3748) -> Vec<usize> {
3749    let captured = type_test::captured_binders(condition, body);
3750    let outer = std::mem::replace(&mut state.captured_binders, captured);
3751    let (true_jump_idxs, false_jump_idxs) =
3752        compile_short_circuit_condition(condition, condition_span, v, ctx, state, output, false);
3753    state.captured_binders = outer;
3754    let body_start = output.len();
3755    for j in true_jump_idxs {
3756        set_jmp_size(&mut output[j], (body_start - j) as u16);
3757    }
3758    false_jump_idxs
3759}
3760
3761fn compile_condition(
3762    main_condition: &Expr,
3763    code: &[Expr],
3764    condition_span: Span,
3765    v: &mut Vec<Variable>,
3766    ctx: Ctx,
3767    state: &mut State<'_>,
3768    output: &mut Vec<Instr>,
3769) {
3770    // get first code limit (after which there are only else(if) blocks)
3771    let main_code_limit = code
3772        .iter()
3773        .position(|x| matches!(x, Expr::ElseIfBlock(_, _, _) | Expr::ElseBlock(_)))
3774        .unwrap_or(code.len());
3775
3776    let condition_blocks_count = code.len() - main_code_limit;
3777    // Each entry is the list of false-jump instruction indices for one condition block.
3778    let mut conditional_false_jmp_idxs: Vec<Vec<usize>> =
3779        Vec::with_capacity(condition_blocks_count + 1);
3780    let mut jmp_instr_idx: Vec<usize> = Vec::with_capacity(condition_blocks_count);
3781    let mut condition_markers: Vec<usize> = Vec::with_capacity(condition_blocks_count);
3782
3783    // Compile the main condition, then the body with what the condition
3784    // proved in scope.
3785    let v_len = v.len();
3786    let false_jump_idxs = compile_guard(
3787        main_condition,
3788        condition_span,
3789        &code[0..main_code_limit],
3790        v,
3791        ctx,
3792        state,
3793        output,
3794    );
3795    conditional_false_jmp_idxs.push(false_jump_idxs);
3796
3797    // parse the main code block
3798    let cond_code = compile_expr(
3799        &code[0..main_code_limit],
3800        v,
3801        ctx.advance_offset(output.len() as u16),
3802        state,
3803    );
3804    output.extend(cond_code);
3805    v.truncate(v_len);
3806    if main_code_limit != code.len() {
3807        output.push(Instr::Jmp(0));
3808        jmp_instr_idx.push(output.len() - 1);
3809    }
3810
3811    for elem in &code[main_code_limit..] {
3812        if let Expr::ElseIfBlock(condition, code, condition_span) = elem {
3813            condition_markers.push(output.len());
3814            let false_jump_idxs =
3815                compile_guard(condition, *condition_span, code, v, ctx, state, output);
3816            conditional_false_jmp_idxs.push(false_jump_idxs);
3817            let cond_code = compile_expr(code, v, ctx.advance_offset(output.len() as u16), state);
3818            output.extend(cond_code);
3819            v.truncate(v_len);
3820            output.push(Instr::Jmp(0));
3821            jmp_instr_idx.push(output.len() - 1);
3822        } else if let Expr::ElseBlock(code) = elem {
3823            condition_markers.push(output.len());
3824            let cond_code = compile_expr(code, v, ctx.advance_offset(output.len() as u16), state);
3825            output.extend(cond_code);
3826        }
3827    }
3828
3829    for y in jmp_instr_idx {
3830        let diff = output.len() - y;
3831        output[y] = Instr::Jmp(diff as u16);
3832    }
3833    // Fix all false-jump instructions for each condition block
3834    for (cm_idx, false_idxs) in conditional_false_jmp_idxs.iter().enumerate() {
3835        let target = if cm_idx < condition_markers.len() {
3836            condition_markers[cm_idx]
3837        } else {
3838            output.len()
3839        };
3840        for &y in false_idxs {
3841            set_jmp_size(&mut output[y], (target - y) as u16);
3842        }
3843    }
3844}
3845
3846fn compile_while_loop(
3847    condition: &Expr,
3848    code: &[Expr],
3849    condition_span: Span,
3850    v: &mut Vec<Variable>,
3851    ctx: Ctx,
3852    state: &mut State<'_>,
3853    output: &mut Vec<Instr>,
3854) {
3855    let output_len_before = output.len();
3856
3857    let v_len = v.len();
3858    let false_jump_idxs = compile_guard(condition, condition_span, code, v, ctx, state, output);
3859
3860    // parse the code block, clone the vars to avoid overriding anything
3861    let loop_id = ctx.block_id + 1;
3862
3863    let mut cond_code = compile_expr(
3864        code,
3865        v,
3866        ctx.no_single_run().advance_offset(output.len() as u16),
3867        state,
3868    );
3869    v.truncate(v_len);
3870
3871    let exit = output.len() + cond_code.len() + 1;
3872    for j in false_jump_idxs {
3873        set_jmp_size(&mut output[j], (exit - j) as u16);
3874    }
3875
3876    let cond_len = (output.len() - output_len_before) as u16;
3877    let body_len = cond_code.len() as u16;
3878    let len = cond_len + body_len; // full span used by JmpBack
3879    // Break/Continue offsets are relative to cond_code, so pass body_len+1 (body remaining + JmpBack)
3880    parse_loop_flow_control(&mut cond_code, loop_id, body_len + 1, false, false);
3881    output.extend(cond_code);
3882    output.push(Instr::JmpBack(len));
3883}
3884
3885fn compile_for_loop(
3886    var_name: &SmolStr,
3887    array: &Expr,
3888    code: &[Expr],
3889    span: Span,
3890    v: &mut Vec<Variable>,
3891    ctx: Ctx,
3892    state: &mut State<'_>,
3893    output: &mut Vec<Instr>,
3894) {
3895    if state.optimize && compile_slice_for_loop(var_name, array, code, span, v, ctx, state, output)
3896    {
3897        return;
3898    }
3899
3900    // parse the array, get its id (the target array is the first Expr in array_code)
3901    let array_type = array.infer_type(v, ctx, state);
3902    let mut array = array
3903        .compile(v, ctx, state, output, None, false, true)
3904        .unwrap_id();
3905
3906    // Iterating a map walks its keys: materialize the key array and iterate that
3907    // with the ordinary array machinery. The loop variable binds each key.
3908    if matches!(array_type, DataType::Map(_)) {
3909        let keys_reg = state.alloc_reg();
3910        output.push(Instr::CallLibFunc(LibFunc::Keys, array, keys_reg));
3911        array = keys_reg;
3912    }
3913
3914    let array_len_id = state.alloc_reg();
3915
3916    output.push(Instr::CallLibFunc(LibFunc::Len, array, array_len_id));
3917
3918    // set up the id of the index variable (0..len)
3919    let index_id = if ctx.single_run {
3920        state.registers.push(0.into());
3921        (state.registers.len() - 1) as u16
3922    } else {
3923        let id = state.alloc_reg();
3924        output.push(Instr::SetInt(id, 0));
3925        id
3926    };
3927    compile_walk(
3928        var_name,
3929        &array_type,
3930        array,
3931        index_id,
3932        array_len_id,
3933        code,
3934        span,
3935        v,
3936        ctx,
3937        state,
3938        output,
3939    );
3940}
3941
3942/// Compiles `for x in list[start..end]` to walk `list` itself from `start` to
3943/// `end`, without building the slice, and answers whether it did. A release
3944/// profile pass.
3945///
3946/// A slice is a copy, so the loop sees the elements as they were when it
3947/// began. Walking the list itself sees the same elements as long as nothing
3948/// rewrites one of them, removes one or reorders them during the loop, so this
3949/// is done only for a body that can do none of that to any list: it runs no
3950/// candela code (no call, no method but `push`, and no operator a program
3951/// could have written as a method) and writes no element of a list. A `push`
3952/// adds past the end of a list and moves nothing, so the elements the walk
3953/// reads stay put even when it pushes onto the list being walked. It pushes
3954/// only onto variables declared outside the loop, whose type says they hold a
3955/// list and not a struct with a `push` method of its own.
3956///
3957/// A range the list cannot supply sends the loop down the path that builds
3958/// the slice, which raises the out-of-range error the slice always raised,
3959/// from the same place.
3960fn compile_slice_for_loop(
3961    var_name: &SmolStr,
3962    array: &Expr,
3963    code: &[Expr],
3964    span: Span,
3965    v: &mut Vec<Variable>,
3966    ctx: Ctx,
3967    state: &mut State<'_>,
3968    output: &mut Vec<Instr>,
3969) -> bool {
3970    let Expr::ArrayGetSlice(list, start, end, slice_span) = array else {
3971        return false;
3972    };
3973    // An operator on a struct or an enum can be a method written in candela,
3974    // which the body could not be seen not to call without typing it.
3975    if state.generics.defines_operator_methods() {
3976        return false;
3977    }
3978    let mut receivers: Vec<SmolStr> = Vec::new();
3979    let mut declared: Vec<SmolStr> = vec![var_name.clone()];
3980    if !block_changes_only_pushed_lists(code, &mut receivers, &mut declared) {
3981        return false;
3982    }
3983    for name in &receivers {
3984        // A name the body declares is not the variable found out here.
3985        if declared.contains(name)
3986            || !v
3987                .iter()
3988                .rfind(|var| var.name == *name)
3989                .is_some_and(|var| matches!(var.var_type, DataType::Array(_)))
3990        {
3991            return false;
3992        }
3993    }
3994    let array_type = list.infer_type(v, ctx, state);
3995    if !matches!(array_type, DataType::Array(_))
3996        || start.infer_type(v, ctx, state) != DataType::Int
3997        || end
3998            .as_ref()
3999            .is_some_and(|end| end.infer_type(v, ctx, state) != DataType::Int)
4000    {
4001        return false;
4002    }
4003
4004    let list_id = list
4005        .compile(v, ctx, state, output, None, false, true)
4006        .unwrap_id();
4007    let start_id = start
4008        .compile(v, ctx, state, output, None, false, true)
4009        .unwrap_id();
4010    let end_id = if let Some(end) = end {
4011        end.compile(v, ctx, state, output, None, false, true)
4012            .unwrap_id()
4013    } else {
4014        // `a[i..]` runs to the end of the list.
4015        let len_id = state.alloc_reg();
4016        output.push(Instr::CallLibFunc(LibFunc::Len, list_id, len_id));
4017        len_id
4018    };
4019
4020    // The walk reads the list, the index and the end out of registers of its
4021    // own, so a body that reassigns a variable they came from changes nothing.
4022    let walked = state.alloc_reg();
4023    let index_id = state.alloc_reg();
4024    let end_at = state.alloc_reg();
4025    let zero = state.const_int_register(0);
4026
4027    // In range means 0 <= start <= end <= len. Each test jumps to the path
4028    // that builds the slice, patched once its place is known.
4029    let mut to_copy: Vec<usize> = Vec::new();
4030    output.push(Instr::CallLibFunc(LibFunc::Len, list_id, end_at));
4031    to_copy.push(output.len());
4032    output.push(Instr::InfIntJmp(start_id, zero, 0));
4033    to_copy.push(output.len());
4034    output.push(Instr::SupIntJmp(end_id, end_at, 0));
4035    to_copy.push(output.len());
4036    output.push(Instr::SupIntJmp(start_id, end_id, 0));
4037    output.push(Instr::Mov(list_id, walked));
4038    output.push(Instr::Mov(start_id, index_id));
4039    output.push(Instr::Mov(end_id, end_at));
4040    let skip_copy = output.len();
4041    output.push(Instr::Jmp(0));
4042
4043    let copy_at = output.len();
4044    for at in to_copy {
4045        set_jmp_size(&mut output[at], (copy_at - at) as u16);
4046    }
4047    output.push(Instr::StoreFuncArg(end_id));
4048    output.push(Instr::GetSliceArray(list_id, start_id, walked));
4049    state.add_to_src(ctx, output, *slice_span);
4050    output.push(Instr::SetInt(index_id, 0));
4051    output.push(Instr::CallLibFunc(LibFunc::Len, walked, end_at));
4052    let copy_len = (output.len() - skip_copy) as u16;
4053    set_jmp_size(&mut output[skip_copy], copy_len);
4054
4055    state.free_reg(start_id, v);
4056    state.free_reg(end_id, v);
4057    compile_walk(
4058        var_name,
4059        &array_type,
4060        walked,
4061        index_id,
4062        end_at,
4063        code,
4064        span,
4065        v,
4066        ctx,
4067        state,
4068        output,
4069    );
4070    true
4071}
4072
4073/// Whether running `code` can change no list but by `push` onto a variable,
4074/// collecting those variables in `receivers` and every name the code declares
4075/// in `declared`. See [`compile_slice_for_loop`].
4076fn block_changes_only_pushed_lists(
4077    code: &[Expr],
4078    receivers: &mut Vec<SmolStr>,
4079    declared: &mut Vec<SmolStr>,
4080) -> bool {
4081    code.iter()
4082        .all(|expr| changes_only_pushed_lists(expr, receivers, declared))
4083}
4084
4085/// [`block_changes_only_pushed_lists`] for one expression or statement.
4086fn changes_only_pushed_lists(
4087    expr: &Expr,
4088    receivers: &mut Vec<SmolStr>,
4089    declared: &mut Vec<SmolStr>,
4090) -> bool {
4091    match expr {
4092        Expr::Var(..)
4093        | Expr::Int(_)
4094        | Expr::Float(_)
4095        | Expr::Bool(_)
4096        | Expr::String(_)
4097        | Expr::Null
4098        | Expr::Break
4099        | Expr::Continue => true,
4100        Expr::GetStructField(inner, _, _, _)
4101        | Expr::BoolNeg(inner, _, _)
4102        | Expr::Neg(inner, _, _)
4103        | Expr::BitNot(inner, _, _) => changes_only_pushed_lists(inner, receivers, declared),
4104        Expr::ArrayGetIndex(l, r, _)
4105        | Expr::Mul(l, r, _, _)
4106        | Expr::Div(l, r, _, _)
4107        | Expr::Add(l, r, _, _)
4108        | Expr::Sub(l, r, _, _)
4109        | Expr::Mod(l, r, _, _)
4110        | Expr::Pow(l, r, _, _)
4111        | Expr::BitAnd(l, r, _, _)
4112        | Expr::BitOr(l, r, _, _)
4113        | Expr::BitXor(l, r, _, _)
4114        | Expr::Shl(l, r, _, _)
4115        | Expr::Shr(l, r, _, _)
4116        | Expr::Eq(l, r, _, _)
4117        | Expr::NotEq(l, r, _, _)
4118        | Expr::Sup(l, r, _, _)
4119        | Expr::SupEq(l, r, _, _)
4120        | Expr::Inf(l, r, _, _)
4121        | Expr::InfEq(l, r, _, _)
4122        | Expr::BoolAnd(l, r, _, _)
4123        | Expr::BoolOr(l, r, _, _) => {
4124            changes_only_pushed_lists(l, receivers, declared)
4125                && changes_only_pushed_lists(r, receivers, declared)
4126        }
4127        Expr::VarDeclare(name, value, _) => {
4128            declared.push(name.clone());
4129            changes_only_pushed_lists(value, receivers, declared)
4130        }
4131        Expr::VarAssign(_, value, _, _) => changes_only_pushed_lists(value, receivers, declared),
4132        Expr::ReturnVal(value) => value
4133            .as_ref()
4134            .as_ref()
4135            .is_none_or(|value| changes_only_pushed_lists(value, receivers, declared)),
4136        Expr::ObjFunctionCall(obj, args, namespace, _, _, _, type_args) => {
4137            let Expr::Var(name, _) = obj.as_ref() else {
4138                return false;
4139            };
4140            if !matches!(&**namespace, [method] if method == "push")
4141                || !type_args.is_empty()
4142                || !args
4143                    .iter()
4144                    .all(|arg| changes_only_pushed_lists(arg, receivers, declared))
4145            {
4146                return false;
4147            }
4148            if !receivers.contains(name) {
4149                receivers.push(name.clone());
4150            }
4151            true
4152        }
4153        Expr::Condition(condition, body, _, _)
4154        | Expr::ElseIfBlock(condition, body, _)
4155        | Expr::WhileBlock(condition, body, _) => {
4156            changes_only_pushed_lists(condition, receivers, declared)
4157                && block_changes_only_pushed_lists(body, receivers, declared)
4158        }
4159        Expr::ElseBlock(body) | Expr::EvalBlock(body) | Expr::LoopBlock(body) => {
4160            block_changes_only_pushed_lists(body, receivers, declared)
4161        }
4162        _ => false,
4163    }
4164}
4165
4166/// Compiles the turns of a `for` loop over a list, a string or a map's keys:
4167/// the loop variable takes `array[index]` for each `index` from where
4168/// `index_id` starts up to the `int` in `end_id`, and `code` runs for each.
4169fn compile_walk(
4170    var_name: &SmolStr,
4171    array_type: &DataType,
4172    array: u16,
4173    index_id: u16,
4174    array_len_id: u16,
4175    code: &[Expr],
4176    span: Span,
4177    v: &mut Vec<Variable>,
4178    ctx: Ctx,
4179    state: &mut State<'_>,
4180    output: &mut Vec<Instr>,
4181) {
4182    let real_var = var_name.as_str() != "_";
4183
4184    // set up the variable for the current element (for current_element_id in ... {}) => current_element_id = array[index]
4185    let current_element_id = if real_var { state.alloc_reg() } else { 0 };
4186
4187    // A closure written in the body takes the element of the turn it was
4188    // written on, so the cell is allocated inside the loop and the body reads
4189    // the variable through it.
4190    let captured = real_var && code_captures_variable(var_name, code);
4191    let element_cell_id = if captured { state.alloc_reg() } else { 0 };
4192
4193    let v_len = v.len();
4194
4195    let is_str = *array_type == DataType::String;
4196
4197    if real_var {
4198        v.push(Variable {
4199            declared: None,
4200            name: var_name.clone(),
4201            register_id: if captured {
4202                element_cell_id
4203            } else {
4204                current_element_id
4205            },
4206            cell: captured,
4207            var_type: match array_type {
4208                DataType::String => DataType::String,
4209                DataType::Array(a_type) => a_type.clone().map_or(DataType::Null, |t| *t),
4210                // A map iterates its keys; the loop variable is a key.
4211                DataType::Map(m) => m.0.clone().unwrap_or(DataType::Unknown),
4212                t => {
4213                    error_type_not_indexable(
4214                        t,
4215                        span,
4216                        true,
4217                        ctx.file_idx,
4218                        state.sources,
4219                        state.type_names(),
4220                    );
4221                }
4222            },
4223        });
4224    }
4225    let loop_id = ctx.block_id + 1;
4226
4227    // accounts for the GetIndexArray/GetIndexString instruction and, where the
4228    // body captures the element, the cell it goes into
4229    let pending = u16::from(real_var) + u16::from(captured);
4230
4231    // The loop is laid out like a counted range:
4232    // ----
4233    // (1) if i >= len jump out
4234    // (2) element = array[i], then the body
4235    // (3) i += 1, and if i < len jump back to (2)
4236    // ----
4237    // so a turn runs one test rather than a test at the top and a jump back
4238    // to it. The length is read once, before the first turn, either way.
4239    let jmp_idx = output.len();
4240    output.push(Instr::SupEqIntJmp(index_id, array_len_id, 0));
4241
4242    let regs_before = state.registers.len() as u16;
4243    let body = compile_expr(
4244        code,
4245        v,
4246        ctx.no_single_run()
4247            .advance_offset(output.len() as u16 + pending),
4248        state,
4249    );
4250    // Clean up variables
4251    v.truncate(v_len);
4252    state.free_scope_registers(regs_before, &body, v);
4253    let body_len = body.len() as u16;
4254
4255    // load the element's value into the current_element_id register
4256    if real_var {
4257        if is_str {
4258            output.push(Instr::GetIndexString(array, index_id, current_element_id));
4259            // A body that assigns a shorter string to what is being walked
4260            // leaves the index past its end, so this is the one loop whose
4261            // element read can raise: the span points the report at it.
4262            state.add_to_src(ctx, output, span);
4263        } else {
4264            output.push(Instr::GetIndexArray(array, index_id, current_element_id));
4265        }
4266        if captured {
4267            output.push(Instr::NewCell(current_element_id, element_cell_id));
4268        }
4269    }
4270    output.extend(body);
4271    // step the index and jump back to the element read while elements remain
4272    output.push(Instr::StepIntJmpBack(
4273        index_id,
4274        array_len_id,
4275        body_len + pending,
4276    ));
4277
4278    let exit_size = (output.len() - jmp_idx) as u16;
4279    output[jmp_idx] = Instr::SupEqIntJmp(index_id, array_len_id, exit_size);
4280    parse_loop_flow_control(&mut output[jmp_idx + 1..], loop_id, exit_size, true, false);
4281
4282    if ctx.single_run {
4283        state.free_reg(array_len_id, v);
4284        state.free_reg(index_id, v);
4285        if real_var {
4286            state.free_reg(current_element_id, v);
4287        }
4288    }
4289}
4290
4291fn compile_int_for_loop(
4292    var_name: &SmolStr,
4293    start_elem: &Expr,
4294    end_elem: &Expr,
4295    code: &[Expr],
4296    span1: Span,
4297    span2: Span,
4298    v: &mut Vec<Variable>,
4299    ctx: Ctx,
4300    state: &mut State<'_>,
4301    output: &mut Vec<Instr>,
4302) {
4303    // IntForLoop is compiled to:
4304    // ----
4305    // (1) if i >= end_elem jump out
4306    // (2) loop_body
4307    // (3) i += 1, and if i < end_elem jump back to body
4308    // ----
4309    //
4310    //
4311    // Check start and elem type
4312    let t1 = start_elem.infer_type(v, ctx, state);
4313    let t2 = end_elem.infer_type(v, ctx, state);
4314    if t1 != DataType::Int {
4315        error_range_invalid_type(span1, &t1, ctx.file_idx, state.sources, state.type_names());
4316    }
4317    if t2 != DataType::Int {
4318        error_range_invalid_type(span2, &t2, ctx.file_idx, state.sources, state.type_names());
4319    }
4320    // The counter is always its own register, filled from the start
4321    // expression. Counting in the start's register was fine for a loop that
4322    // ran once, until the register was shared: a literal start is the
4323    // constant every earlier-compiled function reads for that literal, and a
4324    // variable start is the variable itself, so the loop would advance both.
4325    let output_len = output.len();
4326    let start_elem_id = start_elem
4327        .compile(v, ctx, state, output, None, false, true)
4328        .unwrap_id();
4329    let start_val = state.registers[start_elem_id as usize];
4330    let elem_id = state.alloc_reg();
4331    // An `int` wider than the immediate operand comes out of its register.
4332    if let Some(n) = int_immediate(start_val)
4333        && state.const_registers.values().any(|&v| v == start_elem_id)
4334    {
4335        output.push(Instr::SetInt(elem_id, n));
4336    } else if !write_scratch_into(output, output_len, start_elem_id, elem_id, v, state) {
4337        output.push(Instr::Mov(start_elem_id, elem_id));
4338    }
4339    let end_elem_id = end_elem
4340        .compile(v, ctx, state, output, None, false, true)
4341        .unwrap_id();
4342
4343    // A closure written in the body takes the counter of the turn it was
4344    // written on, so the cell is filled at the top of each turn.
4345    let captured = code_captures_variable(var_name, code);
4346    let elem_cell_id = if captured { state.alloc_reg() } else { 0 };
4347
4348    let v_len = v.len();
4349    v.push(Variable {
4350        declared: None,
4351        name: var_name.clone(),
4352        register_id: if captured { elem_cell_id } else { elem_id },
4353        cell: captured,
4354        var_type: DataType::Int,
4355    });
4356    let loop_id = ctx.block_id + 1;
4357
4358    // (1) if i >= end_elem jump out -> push placeholder first so that compile_expr sees the correct offset
4359    let jmp_idx = output.len();
4360    output.push(Instr::SupEqIntJmp(elem_id, end_elem_id, 0));
4361    if captured {
4362        output.push(Instr::NewCell(elem_id, elem_cell_id));
4363    }
4364
4365    let regs_before = state.registers.len() as u16;
4366    let compiled_loop_code = compile_expr(
4367        code,
4368        v,
4369        ctx.no_single_run().advance_offset(output.len() as u16),
4370        state,
4371    );
4372    state.free_scope_registers(regs_before, &compiled_loop_code, v);
4373    let compiled_loop_code_len = compiled_loop_code.len() as u16;
4374
4375    // (2) loop_body
4376    output.extend(compiled_loop_code);
4377
4378    // (3) i += 1, and if i < end_elem jump back to the top of the turn,
4379    // which is the cell where a body that captures the counter reads it from
4380    output.push(Instr::StepIntJmpBack(
4381        elem_id,
4382        end_elem_id,
4383        compiled_loop_code_len + u16::from(captured),
4384    ));
4385
4386    let exit_size = (output.len() - jmp_idx) as u16;
4387    output[jmp_idx] = Instr::SupEqIntJmp(elem_id, end_elem_id, exit_size);
4388
4389    parse_loop_flow_control(&mut output[jmp_idx + 1..], loop_id, exit_size, true, false);
4390    v.truncate(v_len);
4391
4392    if ctx.single_run {
4393        state.free_reg(end_elem_id, v);
4394        state.free_reg(elem_id, v);
4395    }
4396}
4397
4398fn compile_loop_block(
4399    code: &[Expr],
4400    v: &mut Vec<Variable>,
4401    ctx: Ctx,
4402    state: &mut State<'_>,
4403    output: &mut Vec<Instr>,
4404) {
4405    let loop_id = ctx.block_id + 1;
4406    let regs_before = state.registers.len() as u16;
4407    let mut compiled = compile_expr(
4408        code,
4409        v,
4410        ctx.no_single_run().advance_offset(output.len() as u16),
4411        state,
4412    );
4413    state.free_scope_registers(regs_before, &compiled, v);
4414    let code_length = compiled.len() as u16;
4415    parse_loop_flow_control(&mut compiled, loop_id, code_length + 1, false, true);
4416    output.extend(compiled);
4417    output.push(Instr::JmpBack(code_length));
4418}
4419
4420fn compile_try_catch_block(
4421    e: &[Expr],
4422    err_var: &SmolStr,
4423    catch_code: &[Expr],
4424    v: &mut Vec<Variable>,
4425    ctx: Ctx,
4426    state: &mut State<'_>,
4427    output: &mut Vec<Instr>,
4428) {
4429    output.push(Instr::StartErrorCatch(0, 0)); // patched later on
4430    let err_catch_instr = output.len() - 1;
4431    // A function body is compiled inline at its first call site and records its
4432    // absolute entry address as `ctx.offset + output.len()`, so both blocks are
4433    // compiled at the offset they occupy. Compiling them at the
4434    // enclosing offset makes every call inside a `try` jump short by the length
4435    // of the code already emitted before it.
4436    let main_code = compile_expr(e, v, ctx.advance_offset(output.len() as u16), state);
4437    output.extend(main_code);
4438    output.push(Instr::StopErrorCatch);
4439    output.push(Instr::Jmp(0)); // jumps over the catch handler if no error arises
4440    let jmp_catch_instr = output.len() - 1;
4441
4442    let v_len = v.len();
4443    let err_reg_id = state.alloc_reg();
4444    // The handler starts where the catch jumps to, so a cell for a caught
4445    // error a closure reads is filled there, once per error caught.
4446    let captured = code_captures_variable(err_var, catch_code);
4447    let err_cell_id = if captured { state.alloc_reg() } else { 0 };
4448    v.push(Variable {
4449        declared: None,
4450        name: err_var.clone(),
4451        register_id: if captured { err_cell_id } else { err_reg_id },
4452        cell: captured,
4453        var_type: DataType::String,
4454    });
4455    output[err_catch_instr] =
4456        Instr::StartErrorCatch((output.len() - err_catch_instr) as u16, err_reg_id);
4457    if captured {
4458        output.push(Instr::NewCell(err_reg_id, err_cell_id));
4459    }
4460    let catch_code = compile_expr(
4461        catch_code,
4462        v,
4463        ctx.advance_offset(output.len() as u16),
4464        state,
4465    );
4466    v.truncate(v_len);
4467    output.extend(catch_code);
4468    output[jmp_catch_instr] = Instr::Jmp((output.len() - jmp_catch_instr) as u16);
4469    state.free_reg(err_reg_id, v);
4470}
4471
4472/// The register holding the cell of a variable a closure captures.
4473///
4474/// Every binding a closure can reach puts a captured variable in a cell where
4475/// it is bound, so that each turn around a loop and each call of the declaring
4476/// function gets its own. A binding the analysis did not reach is moved into a
4477/// cell here instead, where the closure is written.
4478fn capture_cell(
4479    name: &SmolStr,
4480    span: Span,
4481    v: &mut [Variable],
4482    ctx: Ctx,
4483    state: &mut State<'_>,
4484    output: &mut Vec<Instr>,
4485) -> u16 {
4486    let Some(pos) = v.iter().rposition(|var| var.name == *name) else {
4487        compiler_errors::error_unknown_variable(name, span, v, ctx.file_idx, state.sources);
4488    };
4489    if v[pos].cell {
4490        return v[pos].register_id;
4491    }
4492    let cell_id = state.alloc_reg();
4493    output.push(Instr::NewCell(v[pos].register_id, cell_id));
4494    v[pos].register_id = cell_id;
4495    v[pos].cell = true;
4496    cell_id
4497}
4498
4499/// Checks a value written into a variable its `let` declared as `declared`,
4500/// the initialiser or a later assignment, and reports one that does not fit.
4501///
4502/// A function written into a `fn(...)` declaration is checked against the
4503/// signature and stands for it from then on, the way a struct field declared
4504/// with one takes it.
4505#[allow(clippy::too_many_arguments)]
4506fn check_declared_value(
4507    name: &str,
4508    declared: &DataType,
4509    declared_span: Span,
4510    value: &Expr,
4511    value_span: Span,
4512    v: &mut Vec<Variable>,
4513    ctx: Ctx,
4514    state: &mut State<'_>,
4515    output: &mut Vec<Instr>,
4516) {
4517    let value_type = value.infer_type(v, ctx, state);
4518    let fn_value = declared_holds_fn_signature(declared);
4519    if fn_value {
4520        check_declared_fn(value, declared, value_span, output, v, ctx, state);
4521    }
4522    if fn_value
4523        && matches!(declared, DataType::FnValue(_))
4524        && matches!(value_type, DataType::Fn(_) | DataType::FnValue(_))
4525    {
4526        return;
4527    }
4528    let fresh = matches!(value, Expr::Array(..) | Expr::Map(..));
4529    if !let_type_fits(declared, &value_type, fresh, state.generics) {
4530        compiler_errors::error_variable_type_mismatch(
4531            ctx.file_idx,
4532            name,
4533            declared_span,
4534            declared,
4535            value_span,
4536            &value_type,
4537            state.sources,
4538            state.type_names(),
4539        );
4540    }
4541}
4542
4543#[allow(clippy::too_many_arguments)]
4544fn compile_var_declaration(
4545    name: &SmolStr,
4546    value: &Expr,
4547    annotation: Option<&LetAnnotation>,
4548    remaining_code: &[Expr],
4549    v: &mut Vec<Variable>,
4550    ctx: Ctx,
4551    state: &mut State<'_>,
4552    output: &mut Vec<Instr>,
4553) {
4554    if let Some(annotation) = annotation {
4555        let declared = annotation.ty.to_datatype(&mut state.type_ctx(ctx.file_idx));
4556        check_declared_value(
4557            name,
4558            &declared,
4559            annotation.span,
4560            value,
4561            annotation.value_span,
4562            v,
4563            ctx,
4564            state,
4565            output,
4566        );
4567        bind_variable(
4568            name,
4569            value,
4570            declared.clone(),
4571            Some(declared),
4572            remaining_code,
4573            v,
4574            ctx,
4575            state,
4576            output,
4577        );
4578        return;
4579    }
4580    let var_type = value.infer_type(v, ctx, state);
4581    // A variable a later assignment writes a function into holds a function
4582    // value: the name no longer says which function, so a call through it
4583    // dispatches on what the variable holds by then. The assignment is looked
4584    // for in the code, not in the types, because a closure written further down
4585    // reads the scope where it is written, not the scope here.
4586    let var_type = match &var_type {
4587        DataType::Fn(fn_id) if code_modifies_variable(name, remaining_code) => {
4588            DataType::FnValue(Box::new(FnValue::from_set(state.new_fn_value_set(*fn_id))))
4589        }
4590        _ => var_type,
4591    };
4592    // A `let` has no annotation to read a function against, so the binding
4593    // takes the function's own signature: the name stops naming one call target
4594    // and holds a value the call dispatches on.
4595    let var_type = if matches!(var_type, DataType::Fn(_)) {
4596        bare_fn_value_type(value, v, ctx, state).unwrap_or(var_type)
4597    } else {
4598        var_type
4599    };
4600    bind_variable(
4601        name,
4602        value,
4603        var_type,
4604        None,
4605        remaining_code,
4606        v,
4607        ctx,
4608        state,
4609        output,
4610    );
4611}
4612
4613/// Compiles the value a `let` starts its variable with and brings the variable
4614/// into scope, typed `var_type`.
4615#[allow(clippy::too_many_arguments)]
4616fn bind_variable(
4617    name: &SmolStr,
4618    value: &Expr,
4619    var_type: DataType,
4620    declared: Option<DataType>,
4621    remaining_code: &[Expr],
4622    v: &mut Vec<Variable>,
4623    ctx: Ctx,
4624    state: &mut State<'_>,
4625    output: &mut Vec<Instr>,
4626) {
4627    let as_fn_value = matches!(var_type, DataType::FnValue(_));
4628    // A variable a closure reads lives in a cell instead of a register, so the
4629    // scope that declared it and the closures that took it share one slot. The
4630    // cell is allocated where the declaration runs, which gives each turn
4631    // around a loop and each call of the declaring function its own.
4632    let captured = code_captures_variable(name, remaining_code);
4633
4634    let output_len = output.len();
4635    let src_id = if as_fn_value {
4636        compile_fn_value(value, v, ctx, state, output, None)
4637    } else {
4638        value
4639            .compile(v, ctx, state, output, None, ctx.single_run, true)
4640            .unwrap_id()
4641    };
4642    // The value can come back in a register something else owns: another
4643    // variable's, or a constant's. The two names may share it only while
4644    // neither is written, or writing one would change the other.
4645    let owned_elsewhere = v.iter().any(|var| var.register_id == src_id)
4646        || state.const_registers.values().any(|&reg| reg == src_id);
4647    let owner_written = v
4648        .iter()
4649        .filter(|var| var.register_id == src_id)
4650        .any(|var| code_modifies_variable(&var.name, remaining_code));
4651    let written = code_modifies_variable(name, remaining_code);
4652    // Code that runs once gives a literal a register of its own, so only a
4653    // shared register needs a copy there.
4654    let needs_own =
4655        !captured && (owner_written || (written && (owned_elsewhere || !ctx.single_run)));
4656    let var_id = if needs_own {
4657        let mutable_id = state.alloc_reg();
4658        if !write_scratch_into(output, output_len, src_id, mutable_id, v, state) {
4659            move_reg_to_reg(output, src_id, mutable_id, state.registers[src_id as usize]);
4660        }
4661        mutable_id
4662    } else {
4663        src_id
4664    };
4665    let var_id = if captured {
4666        let cell_id = state.alloc_reg();
4667        output.push(Instr::NewCell(var_id, cell_id));
4668        state.free_reg(var_id, v);
4669        cell_id
4670    } else {
4671        var_id
4672    };
4673
4674    if let DataType::Fn(fn_id) = &var_type {
4675        let fn_id = *fn_id as usize;
4676        // A closure has no name of its own, so the one it is bound to is the
4677        // one its body calls itself by. The binding is what makes that call
4678        // reach the closure being built rather than whatever else the name
4679        // resolved to, and what makes it a recursive call, which is what saves
4680        // the registers the closure still reads once it returns.
4681        if state.fns[fn_id].direct_calls.contains(name) {
4682            state.fns[fn_id].is_recursive = Some(true);
4683        }
4684        let fn_symbol = SymbolKind::Fn(fn_id as u16);
4685        state
4686            .scope_mut(ctx.file_idx)
4687            .symbols
4688            .push((name.clone(), fn_symbol));
4689    }
4690    v.push(Variable {
4691        declared,
4692        name: name.clone(),
4693        register_id: var_id,
4694        cell: captured,
4695        var_type,
4696    });
4697}
4698
4699/// Adds `fn_id` to the set a variable of type `held` dispatches over, and
4700/// compiles it for every call already made through that set.
4701fn join_fn_value_set(
4702    held: &FnValue,
4703    fn_id: u16,
4704    span: Span,
4705    output: &mut Vec<Instr>,
4706    v: &mut Vec<Variable>,
4707    ctx: Ctx,
4708    state: &mut State<'_>,
4709) {
4710    let Some(set_idx) = held.set else {
4711        return;
4712    };
4713    let Some(set) = state
4714        .indirect_registers
4715        .value_sets
4716        .get_mut(set_idx as usize)
4717    else {
4718        return;
4719    };
4720    if set.candidates.contains(&fn_id) {
4721        return;
4722    }
4723    set.candidates.push(fn_id);
4724    let used_at = set.used_at.clone();
4725    for arg_types in used_at {
4726        ensure_indirect_impl(fn_id as usize, &arg_types, span, output, v, ctx, state);
4727    }
4728}
4729
4730/// Makes the instructions from `from` on write their value into `dest` instead
4731/// of `scratch`, so it does not take a move to get there, and answers whether
4732/// it did.
4733///
4734/// It does when `scratch` holds nothing but that value: the last of those
4735/// instructions writes it, and it is not a variable's register, a constant or
4736/// a register the compiler keeps for itself. A variable written more than once
4737/// needs a register of its own, and this is how its first value lands there
4738/// directly. `scratch` is free again afterwards.
4739fn write_scratch_into(
4740    output: &mut [Instr],
4741    from: usize,
4742    scratch: u16,
4743    dest: u16,
4744    v: &[Variable],
4745    state: &mut State<'_>,
4746) -> bool {
4747    let fresh = output.len() > from
4748        && output[from..]
4749            .iter()
4750            .rev()
4751            .find_map(|instr| instr.get_tgt_id())
4752            == Some(scratch)
4753        && !v.iter().any(|var| var.register_id == scratch)
4754        && !state.const_registers.values().any(|&reg| reg == scratch)
4755        && !state.reserved_registers.contains(&scratch);
4756    if !fresh || !move_to_id(&mut output[from..], dest) {
4757        return false;
4758    }
4759    state.free_reg(scratch, v);
4760    true
4761}
4762
4763#[allow(clippy::too_many_arguments)]
4764fn compile_var_assignment(
4765    name: &SmolStr,
4766    value: &Expr,
4767    span: Span,
4768    value_span: Span,
4769    v: &mut Vec<Variable>,
4770    ctx: Ctx,
4771    state: &mut State<'_>,
4772    output: &mut Vec<Instr>,
4773) {
4774    let var_pos = v.iter().rposition(|x| x.name == *name).unwrap_or_else(|| {
4775        compiler_errors::error_unknown_variable(name, span, v, ctx.file_idx, state.sources);
4776    });
4777    // A variable its `let` declared a type for keeps that type: the value has
4778    // to fit it, and reads after the assignment see the declared type.
4779    let var_type = if let Some(declared) = v[var_pos].declared.clone() {
4780        check_declared_value(
4781            name, &declared, span, value, value_span, v, ctx, state, output,
4782        );
4783        declared
4784    } else {
4785        value.infer_type(v, ctx, state)
4786    };
4787    let id = v[var_pos].register_id;
4788
4789    let var_type = match (&v[var_pos].var_type, &var_type) {
4790        (DataType::FnValue(held), DataType::Fn(fn_id)) if held.sig.is_none() => {
4791            let held = held.clone();
4792            join_fn_value_set(&held, *fn_id, span, output, v, ctx, state);
4793            DataType::FnValue(held)
4794        }
4795        _ => var_type,
4796    };
4797    let as_fn_value = matches!(var_type, DataType::FnValue(_));
4798
4799    // A captured variable is written through its cell, which is the slot the
4800    // closures that took it read.
4801    if v[var_pos].cell {
4802        let value_id = if as_fn_value {
4803            compile_fn_value(value, v, ctx, state, output, None)
4804        } else {
4805            value
4806                .compile(v, ctx, state, output, None, false, true)
4807                .unwrap_id()
4808        };
4809        output.push(Instr::StoreCell(id, value_id));
4810        state.free_reg(value_id, v);
4811        v[var_pos].var_type = var_type;
4812        return;
4813    }
4814
4815    if var_type == DataType::Int {
4816        // (is_inc, src_var_name)
4817        let inc_dec: Option<(bool, &str)> = match value {
4818            // var+1/1+var use the dedicated IncInt/IncIntTo instructions
4819            Expr::Add(l, r, _, _) => {
4820                let src = if matches!(r.as_ref(), Expr::Int(1)) {
4821                    Some(l.as_ref())
4822                } else if matches!(l.as_ref(), Expr::Int(1)) {
4823                    Some(r.as_ref())
4824                } else {
4825                    None
4826                };
4827                src.and_then(|e| {
4828                    if let Expr::Var(src_name, _) = e {
4829                        v.iter()
4830                            .rfind(|x| x.name == *src_name)
4831                            .filter(|x| x.var_type == DataType::Int && !x.cell)
4832                            .map(|_| (true, src_name.as_str()))
4833                    } else {
4834                        None
4835                    }
4836                })
4837            }
4838            // var-1 uses the dedicated DecInt/DecIntTo instructions
4839            Expr::Sub(l, r, _, _) => {
4840                if matches!(r.as_ref(), Expr::Int(1)) {
4841                    if let Expr::Var(src_name, _) = l.as_ref() {
4842                        v.iter()
4843                            .rfind(|x| x.name == *src_name)
4844                            .filter(|x| x.var_type == DataType::Int && !x.cell)
4845                            .map(|_| (false, src_name.as_str()))
4846                    } else {
4847                        None
4848                    }
4849                } else {
4850                    None
4851                }
4852            }
4853            _ => None,
4854        };
4855        if let Some((is_inc, src_name)) = inc_dec {
4856            let src_id = v.iter().rfind(|x| x.name == src_name).unwrap().register_id;
4857            output.push(if src_id == id {
4858                if is_inc {
4859                    Instr::IncInt(id)
4860                } else {
4861                    Instr::DecInt(id)
4862                }
4863            } else {
4864                if is_inc {
4865                    Instr::IncIntTo(src_id, id)
4866                } else {
4867                    Instr::DecIntTo(src_id, id)
4868                }
4869            });
4870            return;
4871        }
4872    }
4873
4874    let output_len = output.len();
4875    let obj_id = value
4876        .compile(v, ctx, state, output, Some(id), false, true)
4877        .unwrap_id();
4878    if output.len() != output_len {
4879        if !move_to_id(output, id) && obj_id != id {
4880            output.push(Instr::Mov(obj_id, id));
4881        }
4882    } else if state.const_registers.values().any(|&v| v == obj_id) {
4883        move_reg_to_reg(output, obj_id, id, state.registers[obj_id as usize]);
4884    } else if obj_id != id {
4885        output.push(Instr::Mov(obj_id, id));
4886    }
4887    if !v
4888        .iter()
4889        .any(|var| &var.name != name && var.register_id == obj_id)
4890    {
4891        state.free_reg(obj_id, v);
4892    }
4893    v[var_pos].var_type = var_type;
4894}
4895
4896#[allow(clippy::too_many_arguments)]
4897fn compile_struct_definition(
4898    name: &SmolStr,
4899    fields: &[(SmolStr, TypeExpr, Span)],
4900    span: Span,
4901    type_params: &TypeParams,
4902    defaults: &[Option<(Expr, Span)>],
4903    ctx: Ctx,
4904    state: &mut State<'_>,
4905    _output: &mut Vec<Instr>,
4906) {
4907    if !type_params.is_empty() {
4908        let template = state.generics.add_struct_template(
4909            name.clone(),
4910            type_params.clone(),
4911            ctx.file_idx,
4912            Box::from(fields),
4913            Box::from(defaults),
4914        );
4915        state
4916            .scope_mut(ctx.file_idx)
4917            .symbols
4918            .push((name.clone(), SymbolKind::Template(template)));
4919        return;
4920    }
4921    let struct_id = state.structs.len() as u16;
4922    state.structs.push(Struct {
4923        // pushing it first allows structs to be recursive
4924        name: name.clone(),
4925        fields: Box::from([]),
4926        id: struct_id,
4927        name_span: span,
4928    });
4929    state
4930        .generics
4931        .set_struct_defaults(struct_id, ctx.file_idx, Box::from(defaults));
4932    let struct_symbol = SymbolKind::Struct((state.structs.len() - 1) as u16);
4933    state
4934        .scope_mut(ctx.file_idx)
4935        .symbols
4936        .push((name.clone(), struct_symbol));
4937    let parsed_fields = fields
4938        .iter()
4939        .map(|(f, f_t, f_span)| {
4940            (
4941                f.clone(),
4942                f_t.to_datatype(&mut state.type_ctx(ctx.file_idx)),
4943                *f_span,
4944            )
4945        })
4946        .collect();
4947    state.structs[struct_id as usize].fields = parsed_fields;
4948}
4949
4950fn compile_function_definition(
4951    fn_name: &SmolStr,
4952    fn_args: &[(SmolStr, Option<TypeExpr>)],
4953    fn_code: &Rc<[Expr]>,
4954    span: Span,
4955    declared_return_type: Option<&(TypeExpr, Span)>,
4956    type_params: &TypeParams,
4957    _v: &mut Vec<Variable>,
4958    ctx: Ctx,
4959    state: &mut State<'_>,
4960    _output: &mut Vec<Instr>,
4961) {
4962    if let Some(func) = state.fns.iter().find(|func| &func.name == fn_name) {
4963        compiler_errors::error_function_already_defined(func, span, ctx.file_idx, state.sources);
4964    }
4965    let mut callees = Vec::new();
4966    collect_direct_fn_calls(fn_code, self_type_of(fn_name), &mut callees);
4967    let fn_symbol = SymbolKind::Fn(state.fns.len() as u16);
4968    state
4969        .scope_mut(ctx.file_idx)
4970        .symbols
4971        .push((fn_name.clone(), fn_symbol));
4972    // An annotation naming one of this function's own type parameters resolves
4973    // per call site, so it is left un-pinned here.
4974    let args: Box<[(SmolStr, Option<DataType>)]> = fn_args
4975        .iter()
4976        .map(|(a, t)| {
4977            (
4978                a.clone(),
4979                t.clone()
4980                    .filter(|t_e| !t_e.mentions_any(type_params))
4981                    .map(|t_e| t_e.to_datatype(&mut state.type_ctx(ctx.file_idx))),
4982            )
4983        })
4984        .collect();
4985    let return_type = declared_return_type
4986        .filter(|(t_e, _)| !t_e.mentions_any(type_params))
4987        .map(|(t_e, t_span)| (t_e.to_datatype(&mut state.type_ctx(ctx.file_idx)), *t_span));
4988    let generics = (!type_params.is_empty()).then(|| {
4989        Box::new(FnGenerics {
4990            params: type_params.clone(),
4991            arg_types: fn_args.iter().map(|(_, t)| t.clone()).collect(),
4992            return_type: declared_return_type.map(|t| Box::new(t.clone())),
4993            bindings: Box::from([]),
4994            file_idx: ctx.file_idx,
4995        })
4996    });
4997    state.fns.push(Function {
4998        name: fn_name.clone(),
4999        args,
5000        code: fn_code.clone(),
5001        impls: Vec::new(),
5002        is_recursive: None,
5003        returns_null: check_if_returns_void(fn_code),
5004        src_file: ctx.file_idx,
5005        return_type_cache: Vec::new(),
5006        direct_calls: callees.into_boxed_slice(),
5007        name_span: span,
5008        return_type,
5009        generics,
5010        captures: Box::from([]),
5011        entry_register: None,
5012    });
5013}
5014
5015fn compile_return(
5016    return_value: Option<&Expr>,
5017    v: &mut Vec<Variable>,
5018    ctx: Ctx,
5019    state: &mut State<'_>,
5020    output: &mut Vec<Instr>,
5021) {
5022    // `main` is compiled inline at the program's top level, so a `return` there
5023    // has no call frame to pop. It ends the program, and its value, like the
5024    // value of `main` itself, has nowhere to go.
5025    if !ctx.in_function {
5026        if let Some(x) = return_value {
5027            let id = x
5028                .compile(v, ctx, state, output, None, false, true)
5029                .unwrap_id();
5030            state.free_reg(id, v);
5031        }
5032        output.push(Instr::Halt(0));
5033        return;
5034    }
5035    let Some(x) = return_value else {
5036        // `return;` leaves the function with no value. A body always ends in a
5037        // `VoidReturn`, but an early return needs one of its own, or control
5038        // falls through into the statements that follow it.
5039        output.push(Instr::VoidReturn);
5040        return;
5041    };
5042    let mut id = x
5043        .compile(v, ctx, state, output, None, false, true)
5044        .unwrap_id();
5045    // A value typed `any` leaving a function declared to return a concrete type
5046    // is checked here, the one place it can be: the call site reads the
5047    // declared type and compiles against it. The wrong value raises the
5048    // catchable `bad_downcast`.
5049    if let Some(check) = ctx.return_downcast
5050        && matches!(x.infer_type(v, ctx, state), DataType::Unknown)
5051    {
5052        state.free_reg(id, v);
5053        let checked_id = state.alloc_reg();
5054        match check {
5055            ReturnCheck::Builtin(downcast) => {
5056                output.push(Instr::CallLibFunc(downcast, id, checked_id));
5057            }
5058            ReturnCheck::Types(codes) => {
5059                let codes = state.type_codes_register(codes.as_slice());
5060                output.push(Instr::StoreFuncArg(codes));
5061                output.push(Instr::CallLibFunc(LibFunc::AsTypeVal, id, checked_id));
5062            }
5063            ReturnCheck::TypesIn(codes) => {
5064                output.push(Instr::StoreFuncArg(codes));
5065                output.push(Instr::CallLibFunc(LibFunc::AsTypeVal, id, checked_id));
5066            }
5067        }
5068        id = checked_id;
5069    }
5070    if ctx.is_compiling_recursive {
5071        output.push(Instr::RecursiveReturn(id));
5072    } else {
5073        output.push(Instr::Return(id));
5074    }
5075}
5076
5077#[inline]
5078fn compile_loop_break(ctx: Ctx, output: &mut Vec<Instr>) {
5079    output.push(Instr::NotEqJmp(ctx.block_id + 1, 0, 0));
5080}
5081
5082#[inline]
5083fn compile_loop_continue(ctx: Ctx, output: &mut Vec<Instr>) {
5084    output.push(Instr::EqJmp(ctx.block_id + 1, 0, 0));
5085}
5086
5087#[inline]
5088fn compile_eval_block(
5089    code: &[Expr],
5090    v: &mut Vec<Variable>,
5091    ctx: Ctx,
5092    state: &mut State<'_>,
5093    output: &mut Vec<Instr>,
5094) {
5095    output.extend(compile_expr(
5096        code,
5097        v,
5098        ctx.set_offset(output.len() as u16),
5099        state,
5100    ));
5101}
5102
5103/// The type a function's body compiles against, read from its own name: a
5104/// method is registered under the mangled `Type#method`, and an ordinary
5105/// function's name holds no separator.
5106fn self_type_of(fn_name: &str) -> Option<&str> {
5107    fn_name
5108        .split_once(METHOD_SEP)
5109        .map(|(type_name, _)| type_name)
5110}
5111
5112pub fn compile_expr(
5113    input: &[Expr],
5114    v: &mut Vec<Variable>,
5115    ctx: Ctx,
5116    state: &mut State<'_>,
5117) -> Vec<Instr> {
5118    let v_len = v.len();
5119    let symbols_len = state.scope(ctx.file_idx).symbols.len();
5120    let mut output: Vec<Instr> = Vec::with_capacity(input.len());
5121    for (idx, x) in input.iter().enumerate() {
5122        // Nothing reads the value of a statement, except where the form has no
5123        // statement arm to compile through: `x;` and `1 + 2;` are compiled as
5124        // values, and the register is freed again on the next line.
5125        if let Some(id) = x.compile_with_code_context(
5126            v,
5127            ctx,
5128            state,
5129            &mut output,
5130            None,
5131            false,
5132            &input[idx + 1..],
5133            x.is_value_only(),
5134        ) {
5135            state.free_reg(id, v);
5136        }
5137    }
5138    v.truncate(v_len);
5139    // The block's variables and the names it declared go out of scope with it.
5140    // The function table does not: an entry in it is named by index, by the
5141    // bytecode that calls it and by the `DataType::Fn` a closure's type is, and
5142    // an index nothing can hand back out is an index nothing else can be given.
5143    // A closure is registered under its own source span, so the same body
5144    // compiled again for a second specialisation finds the entry it made the
5145    // first time instead of making a second one.
5146    state.scope_mut(ctx.file_idx).symbols.truncate(symbols_len);
5147    output
5148}
5149
5150impl Expr {
5151    #[must_use]
5152    pub const fn is_constant_literal(&self) -> bool {
5153        matches!(
5154            self,
5155            Self::Int(_) | Self::Float(_) | Self::String(_) | Self::Bool(_) | Self::Null
5156        )
5157    }
5158    #[inline(always)]
5159    pub fn compile(
5160        &self,
5161        v: &mut Vec<Variable>,
5162        ctx: Ctx,
5163        state: &mut State<'_>,
5164        output: &mut Vec<Instr>,
5165        tgt_id: Option<u16>,
5166        var_assignment: bool,
5167        uses_id: bool,
5168    ) -> Option<u16> {
5169        self.compile_with_code_context(v, ctx, state, output, tgt_id, var_assignment, &[], uses_id)
5170    }
5171    pub fn compile_with_code_context(
5172        &self,
5173        v: &mut Vec<Variable>,
5174        ctx: Ctx,
5175        state: &mut State<'_>,
5176        output: &mut Vec<Instr>,
5177        tgt_id: Option<u16>,
5178        var_assignment: bool,
5179        remaining_code: &[Self],
5180        uses_id: bool,
5181    ) -> Option<u16> {
5182        match self {
5183            Self::Int(num) => {
5184                debug_assert!(uses_id);
5185                if var_assignment {
5186                    state.registers.push((*num).into());
5187                    return Some((state.registers.len() - 1) as u16);
5188                }
5189                let data = (*num).into();
5190                if let Some(&id) = state.const_registers.get(&data) {
5191                    Some(id)
5192                } else {
5193                    let id = state.registers.len() as u16;
5194                    state.const_registers.insert(data, id);
5195                    state.registers.push(data);
5196                    Some(id)
5197                }
5198            }
5199            Self::Float(num) => {
5200                debug_assert!(uses_id);
5201                if var_assignment {
5202                    state.registers.push((*num).into());
5203                    return Some((state.registers.len() - 1) as u16);
5204                }
5205                let data = (*num).into();
5206                if let Some(&id) = state.const_registers.get(&data) {
5207                    Some(id)
5208                } else {
5209                    state.registers.push(data);
5210                    let id = (state.registers.len() - 1) as u16;
5211                    state.const_registers.insert(data, id);
5212                    Some(id)
5213                }
5214            }
5215            Self::String(str) => {
5216                debug_assert!(uses_id);
5217                if var_assignment {
5218                    state
5219                        .registers
5220                        .push(Data::p_str(str, &mut state.pools.strings));
5221                    return Some((state.registers.len() - 1) as u16);
5222                }
5223                let data = Data::p_str(str, &mut state.pools.strings);
5224                if let Some(&id) = state.const_registers.get(&data) {
5225                    Some(id)
5226                } else {
5227                    let id = state.registers.len() as u16;
5228                    state.const_registers.insert(data, id);
5229                    state.registers.push(data);
5230                    Some(id)
5231                }
5232            }
5233            Self::Null => {
5234                debug_assert!(uses_id);
5235                if var_assignment {
5236                    state.registers.push(NULL);
5237                    return Some((state.registers.len() - 1) as u16);
5238                }
5239                if let Some(&id) = state.const_registers.get(&NULL) {
5240                    Some(id)
5241                } else {
5242                    let id = state.registers.len() as u16;
5243                    state.const_registers.insert(NULL, id);
5244                    state.registers.push(NULL);
5245                    Some(id)
5246                }
5247            }
5248            Self::Bool(bool) => {
5249                debug_assert!(uses_id);
5250                if var_assignment {
5251                    state.registers.push((*bool).into());
5252                    return Some((state.registers.len() - 1) as u16);
5253                }
5254                let data: Data = (*bool).into();
5255                if let Some(&id) = state.const_registers.get(&data) {
5256                    Some(id)
5257                } else {
5258                    let id = state.registers.len() as u16;
5259                    state.const_registers.insert(data, id);
5260                    state.registers.push(data);
5261                    Some(id)
5262                }
5263            }
5264            Self::Var(name, span) => {
5265                debug_assert!(uses_id);
5266                if let Some(var) = v.iter().rfind(|v_temp| *name == v_temp.name) {
5267                    if var.cell {
5268                        // A captured variable holds a cell; its value is what
5269                        // the cell holds now, which a closure may have written.
5270                        let cell = var.register_id;
5271                        let dest = state.alloc_reg_tgt(tgt_id);
5272                        output.push(Instr::LoadCell(cell, dest));
5273                        Some(dest)
5274                    } else if let DataType::Fn(fn_id) = var.var_type
5275                        && state.fns[fn_id as usize].captures.is_empty()
5276                    {
5277                        // A parameter the call settled on one function that
5278                        // captures nothing is passed nothing, so reading it as
5279                        // a value builds that function's value.
5280                        Some(compile_fn_entry_value(
5281                            fn_id as usize,
5282                            v,
5283                            ctx,
5284                            state,
5285                            output,
5286                            tgt_id,
5287                        ))
5288                    } else {
5289                        Some(var.register_id)
5290                    }
5291                } else if let Some(fn_id) = state.scope(ctx.file_idx).find_function(&[], name) {
5292                    // A bare name that names a function is the function itself
5293                    // wherever it is read rather than called, so a `let`, a
5294                    // return, a collection element and an argument all hand on
5295                    // a value a later call can dispatch on.
5296                    Some(compile_fn_entry_value(fn_id, v, ctx, state, output, tgt_id))
5297                } else if let Some((enum_id, variant_idx)) =
5298                    variant_constructor(std::slice::from_ref(name), &[], *span, v, ctx, state)
5299                {
5300                    Some(compile_enum_construction(
5301                        enum_id,
5302                        variant_idx,
5303                        &[],
5304                        *span,
5305                        &[],
5306                        v,
5307                        ctx,
5308                        state,
5309                        output,
5310                    ))
5311                } else {
5312                    compiler_errors::error_unknown_variable(
5313                        name,
5314                        *span,
5315                        v,
5316                        ctx.file_idx,
5317                        state.sources,
5318                    );
5319                }
5320            }
5321            Self::Array(array_items, spans) => {
5322                debug_assert!(uses_id);
5323                Some(compile_array_literal(
5324                    array_items,
5325                    spans,
5326                    v,
5327                    ctx,
5328                    state,
5329                    output,
5330                ))
5331            }
5332            Self::Struct(namespace, fields, span, type_args, base) => {
5333                debug_assert!(uses_id);
5334                Some(compile_struct_literal(
5335                    namespace,
5336                    fields,
5337                    type_args,
5338                    base.as_deref(),
5339                    *span,
5340                    v,
5341                    ctx,
5342                    state,
5343                    output,
5344                ))
5345            }
5346            Self::StructDefault(struct_id, span) => {
5347                debug_assert!(uses_id);
5348                Some(compile_struct_default(
5349                    *struct_id, *span, v, ctx, state, output,
5350                ))
5351            }
5352            Self::Map(kv_pairs, span) => {
5353                debug_assert!(uses_id);
5354                Some(compile_map_literal(kv_pairs, *span, v, ctx, state, output))
5355            }
5356            Self::GetStructField(struct_expr, field, struct_span, field_span) => {
5357                debug_assert!(uses_id);
5358                Some(compile_struct_field_access(
5359                    struct_expr,
5360                    field,
5361                    *struct_span,
5362                    *field_span,
5363                    v,
5364                    ctx,
5365                    state,
5366                    output,
5367                ))
5368            }
5369            Self::Propagate(operand, span) => {
5370                debug_assert!(uses_id);
5371                Some(compile_propagate(
5372                    operand, *span, tgt_id, v, ctx, state, output,
5373                ))
5374            }
5375            Self::Is(operand, target, operand_span, target_span) => {
5376                debug_assert!(uses_id);
5377                Some(
5378                    type_test::compile_is(
5379                        operand,
5380                        target,
5381                        *operand_span,
5382                        *target_span,
5383                        tgt_id,
5384                        false,
5385                        v,
5386                        ctx,
5387                        state,
5388                        output,
5389                    )
5390                    .0,
5391                )
5392            }
5393            Self::Cast(operand, target, operand_span, target_span) => {
5394                debug_assert!(uses_id);
5395                Some(type_test::compile_cast(
5396                    operand,
5397                    target,
5398                    *operand_span,
5399                    *target_span,
5400                    tgt_id,
5401                    v,
5402                    ctx,
5403                    state,
5404                    output,
5405                ))
5406            }
5407            // array[index]
5408            Self::ArrayGetIndex(array, index, span) => {
5409                debug_assert!(uses_id);
5410                Some(compile_array_indexing(
5411                    array, index, *span, v, ctx, state, output,
5412                ))
5413            }
5414            // array[start..end]
5415            Self::ArrayGetSlice(array, idx_start, idx_end, span) => {
5416                debug_assert!(uses_id);
5417                Some(compile_array_slice(
5418                    array,
5419                    idx_start,
5420                    idx_end.as_deref(),
5421                    *span,
5422                    v,
5423                    ctx,
5424                    state,
5425                    output,
5426                ))
5427            }
5428            Self::Mul(l, r, span1, span2) => {
5429                debug_assert!(uses_id);
5430                Some(uniform_op2(
5431                    Instr::MulFloat,
5432                    &DataType::Float,
5433                    Instr::MulInt,
5434                    &DataType::Int,
5435                    "*",
5436                    l,
5437                    r,
5438                    *span1,
5439                    *span2,
5440                    tgt_id,
5441                    v,
5442                    ctx,
5443                    state,
5444                    output,
5445                ))
5446            }
5447            Self::Div(l, r, span1, span2) => {
5448                debug_assert!(uses_id);
5449                Some(compile_div_op(
5450                    l, r, *span1, *span2, tgt_id, v, ctx, state, output,
5451                ))
5452            }
5453            Self::Add(l, r, span1, span2) => {
5454                debug_assert!(uses_id);
5455                Some(compile_add_op(
5456                    l, r, *span1, *span2, tgt_id, v, ctx, state, output,
5457                ))
5458            }
5459            Self::Sub(l, r, span1, span2) => {
5460                debug_assert!(uses_id);
5461                Some(compile_sub_op(
5462                    l, r, *span1, *span2, tgt_id, v, ctx, state, output,
5463                ))
5464            }
5465            Self::Mod(l, r, span1, span2) => {
5466                debug_assert!(uses_id);
5467                Some(compile_mod_op(
5468                    l, r, *span1, *span2, tgt_id, v, ctx, state, output,
5469                ))
5470            }
5471            Self::Pow(l, r, span1, span2) => {
5472                debug_assert!(uses_id);
5473                Some(uniform_op2(
5474                    Instr::PowFloat,
5475                    &DataType::Float,
5476                    Instr::PowInt,
5477                    &DataType::Int,
5478                    "^",
5479                    l,
5480                    r,
5481                    *span1,
5482                    *span2,
5483                    tgt_id,
5484                    v,
5485                    ctx,
5486                    state,
5487                    output,
5488                ))
5489            }
5490            Self::BitAnd(l, r, span1, span2) => {
5491                debug_assert!(uses_id);
5492                Some(int_op2(
5493                    Instr::BitAndInt,
5494                    "&",
5495                    l,
5496                    r,
5497                    *span1,
5498                    *span2,
5499                    tgt_id,
5500                    v,
5501                    ctx,
5502                    state,
5503                    output,
5504                ))
5505            }
5506            Self::BitOr(l, r, span1, span2) => {
5507                debug_assert!(uses_id);
5508                Some(int_op2(
5509                    Instr::BitOrInt,
5510                    "|",
5511                    l,
5512                    r,
5513                    *span1,
5514                    *span2,
5515                    tgt_id,
5516                    v,
5517                    ctx,
5518                    state,
5519                    output,
5520                ))
5521            }
5522            Self::BitXor(l, r, span1, span2) => {
5523                debug_assert!(uses_id);
5524                Some(int_op2(
5525                    Instr::BitXorInt,
5526                    "^^",
5527                    l,
5528                    r,
5529                    *span1,
5530                    *span2,
5531                    tgt_id,
5532                    v,
5533                    ctx,
5534                    state,
5535                    output,
5536                ))
5537            }
5538            Self::Shl(l, r, span1, span2) => {
5539                debug_assert!(uses_id);
5540                Some(compile_shift_op(
5541                    Instr::ShlInt,
5542                    "<<",
5543                    l,
5544                    r,
5545                    *span1,
5546                    *span2,
5547                    tgt_id,
5548                    v,
5549                    ctx,
5550                    state,
5551                    output,
5552                ))
5553            }
5554            Self::Shr(l, r, span1, span2) => {
5555                debug_assert!(uses_id);
5556                Some(compile_shift_op(
5557                    Instr::ShrInt,
5558                    ">>",
5559                    l,
5560                    r,
5561                    *span1,
5562                    *span2,
5563                    tgt_id,
5564                    v,
5565                    ctx,
5566                    state,
5567                    output,
5568                ))
5569            }
5570            Self::BitNot(l, span1, span2) => {
5571                debug_assert!(uses_id);
5572                Some(compile_bit_not_op(
5573                    l, *span1, *span2, tgt_id, v, ctx, state, output,
5574                ))
5575            }
5576            Self::Eq(l, r, span1, span2) => {
5577                debug_assert!(uses_id);
5578                Some(compile_eq_op(
5579                    l, r, *span1, *span2, tgt_id, v, ctx, state, output,
5580                ))
5581            }
5582            Self::NotEq(l, r, span1, span2) => {
5583                debug_assert!(uses_id);
5584                Some(compile_neq_op(
5585                    l, r, *span1, *span2, tgt_id, v, ctx, state, output,
5586                ))
5587            }
5588            Self::Sup(l, r, span1, span2) => {
5589                debug_assert!(uses_id);
5590                Some(compile_cmp_op(
5591                    Instr::SupFloat,
5592                    Instr::SupInt,
5593                    Instr::SupStr,
5594                    ">",
5595                    l,
5596                    r,
5597                    *span1,
5598                    *span2,
5599                    tgt_id,
5600                    v,
5601                    ctx,
5602                    state,
5603                    output,
5604                ))
5605            }
5606            Self::SupEq(l, r, span1, span2) => {
5607                debug_assert!(uses_id);
5608                Some(compile_cmp_op(
5609                    Instr::SupEqFloat,
5610                    Instr::SupEqInt,
5611                    Instr::SupEqStr,
5612                    ">=",
5613                    l,
5614                    r,
5615                    *span1,
5616                    *span2,
5617                    tgt_id,
5618                    v,
5619                    ctx,
5620                    state,
5621                    output,
5622                ))
5623            }
5624            Self::Inf(l, r, span1, span2) => {
5625                debug_assert!(uses_id);
5626                Some(compile_cmp_op(
5627                    Instr::InfFloat,
5628                    Instr::InfInt,
5629                    Instr::InfStr,
5630                    "<",
5631                    l,
5632                    r,
5633                    *span1,
5634                    *span2,
5635                    tgt_id,
5636                    v,
5637                    ctx,
5638                    state,
5639                    output,
5640                ))
5641            }
5642            Self::InfEq(l, r, span1, span2) => {
5643                debug_assert!(uses_id);
5644                Some(compile_cmp_op(
5645                    Instr::InfEqFloat,
5646                    Instr::InfEqInt,
5647                    Instr::InfEqStr,
5648                    "<=",
5649                    l,
5650                    r,
5651                    *span1,
5652                    *span2,
5653                    tgt_id,
5654                    v,
5655                    ctx,
5656                    state,
5657                    output,
5658                ))
5659            }
5660            Self::BoolAnd(l, r, span1, span2) => {
5661                debug_assert!(uses_id);
5662                Some(compile_short_circuit_value(
5663                    l, r, *span1, *span2, "&&", tgt_id, v, ctx, state, output, false,
5664                ))
5665            }
5666            Self::BoolOr(l, r, span1, span2) => {
5667                debug_assert!(uses_id);
5668                Some(compile_short_circuit_value(
5669                    l, r, *span1, *span2, "||", tgt_id, v, ctx, state, output, false,
5670                ))
5671            }
5672            Self::Neg(l, span1, span2) => {
5673                debug_assert!(uses_id);
5674                Some(compile_neg_op(
5675                    l, *span1, *span2, tgt_id, v, ctx, state, output,
5676                ))
5677            }
5678            Self::BoolNeg(l, span1, span2) => {
5679                debug_assert!(uses_id);
5680                Some(compile_bool_neg_op(
5681                    l, *span1, *span2, tgt_id, v, ctx, state, output,
5682                ))
5683            }
5684            Self::InlineCondition(main_condition, code, span, condition_span) => {
5685                debug_assert!(uses_id);
5686                let as_fn_value = matches!(self.infer_type(v, ctx, state), DataType::FnValue(_));
5687                Some(compile_inline_condition(
5688                    main_condition,
5689                    code,
5690                    *span,
5691                    *condition_span,
5692                    v,
5693                    ctx,
5694                    state,
5695                    output,
5696                    tgt_id,
5697                    as_fn_value,
5698                ))
5699            }
5700            Self::FunctionCall(args, namespace, markers, args_indexes, type_args) if uses_id => {
5701                Some(
5702                    handle_functions(
5703                        output,
5704                        v,
5705                        ctx,
5706                        state,
5707                        tgt_id,
5708                        args,
5709                        namespace,
5710                        *markers,
5711                        args_indexes,
5712                        type_args,
5713                    )
5714                    .unwrap_or_else(|| {
5715                        if let Some(&id) = state.const_registers.get(&NULL) {
5716                            id
5717                        } else {
5718                            let id = state.registers.len() as u16;
5719                            state.const_registers.insert(NULL, id);
5720                            state.registers.push(NULL);
5721                            id
5722                        }
5723                    }),
5724                )
5725            }
5726            Self::CallValue(callee, args, span, args_indexes) if uses_id => Some(
5727                handle_value_call(
5728                    output,
5729                    v,
5730                    ctx,
5731                    state,
5732                    tgt_id,
5733                    callee,
5734                    args,
5735                    *span,
5736                    args_indexes,
5737                )
5738                .unwrap_or_else(|| {
5739                    if let Some(&id) = state.const_registers.get(&NULL) {
5740                        id
5741                    } else {
5742                        let id = state.registers.len() as u16;
5743                        state.const_registers.insert(NULL, id);
5744                        state.registers.push(NULL);
5745                        id
5746                    }
5747                }),
5748            ),
5749            Self::AnonymousFunction(_, _, span) => {
5750                debug_assert!(uses_id);
5751                let DataType::Fn(fn_id) = self.infer_type(v, ctx, state) else {
5752                    unsafe { unreachable_unchecked() }
5753                };
5754                let fn_id = fn_id as usize;
5755                let captures = state.fns[fn_id].captures.clone();
5756                if captures.is_empty() {
5757                    // A closure that reads nothing around it carries no
5758                    // environment, so its value is where its body starts and
5759                    // nothing else. A call that names it never reads the value,
5760                    // but a position the compiler does not follow, such as an
5761                    // `any`, holds it.
5762                    Some(compile_fn_entry_value(fn_id, v, ctx, state, output, tgt_id))
5763                } else {
5764                    // The value of a closure that captures is where its body
5765                    // starts followed by its environment: the cells of the
5766                    // variables its body reads, in the order the body expects
5767                    // them. Two closures written in one scope take the same
5768                    // cells, so what one writes the other reads.
5769                    let entry_id = state.fn_entry_register(fn_id);
5770                    let env_id = state.alloc_reg_tgt(tgt_id);
5771                    output.push(Instr::EmptyFnValue(env_id));
5772                    output.push(Instr::Push(env_id, entry_id));
5773                    for (name, _, _) in &captures {
5774                        let cell_id = capture_cell(name, *span, v, ctx, state, output);
5775                        output.push(Instr::Push(env_id, cell_id));
5776                    }
5777                    Some(env_id)
5778                }
5779            }
5780
5781            // ------------------
5782            // --- STATEMENTS ---
5783            // ------------------
5784
5785            // x[y] = z;
5786            Self::ArrayModify(array, index, value, index_markers, elem_markers) => {
5787                debug_assert!(!uses_id);
5788                compile_array_index_assignment(
5789                    array,
5790                    index,
5791                    value,
5792                    *index_markers,
5793                    *elem_markers,
5794                    v,
5795                    ctx,
5796                    state,
5797                    output,
5798                );
5799                None
5800            }
5801            Self::SetStructField(
5802                struct_expr,
5803                field,
5804                new_val,
5805                struct_span,
5806                field_span,
5807                value_span,
5808            ) => {
5809                debug_assert!(!uses_id);
5810                compile_struct_field_assignment(
5811                    struct_expr,
5812                    field,
5813                    new_val,
5814                    *struct_span,
5815                    *field_span,
5816                    *value_span,
5817                    v,
5818                    ctx,
5819                    state,
5820                    output,
5821                );
5822                None
5823            }
5824            Self::Condition(main_condition, code, _, condition_span) => {
5825                debug_assert!(!uses_id);
5826                compile_condition(main_condition, code, *condition_span, v, ctx, state, output);
5827                None
5828            }
5829            Self::WhileBlock(condition, code, condition_span) => {
5830                debug_assert!(!uses_id);
5831                compile_while_loop(condition, code, *condition_span, v, ctx, state, output);
5832                None
5833            }
5834            Self::ForLoop(var_name, array, code, span) => {
5835                debug_assert!(!uses_id);
5836                compile_for_loop(var_name, array, code, *span, v, ctx, state, output);
5837                None
5838            }
5839            Self::IntForLoop(var_name, start_elem, end_elem, code, span1, span2) => {
5840                debug_assert!(!uses_id);
5841                compile_int_for_loop(
5842                    var_name, start_elem, end_elem, code, *span1, *span2, v, ctx, state, output,
5843                );
5844                None
5845            }
5846            Self::LoopBlock(code) => {
5847                debug_assert!(!uses_id);
5848                compile_loop_block(code, v, ctx, state, output);
5849                None
5850            }
5851            Self::TryCatchBlock(e, err_var, catch_code) => {
5852                debug_assert!(!uses_id);
5853                compile_try_catch_block(e, err_var, catch_code, v, ctx, state, output);
5854                None
5855            }
5856            Self::VarDeclare(name, value, annotation) => {
5857                debug_assert!(!uses_id);
5858                compile_var_declaration(
5859                    name,
5860                    value,
5861                    annotation.as_deref(),
5862                    remaining_code,
5863                    v,
5864                    ctx,
5865                    state,
5866                    output,
5867                );
5868                None
5869            }
5870            Self::VarAssign(name, value, span, value_span) => {
5871                debug_assert!(!uses_id);
5872                compile_var_assignment(name, value, *span, *value_span, v, ctx, state, output);
5873                None
5874            }
5875            Self::StructDeclare(name, fields, span, type_params, defaults) => {
5876                debug_assert!(!uses_id);
5877                compile_struct_definition(
5878                    name,
5879                    fields,
5880                    *span,
5881                    type_params,
5882                    defaults,
5883                    ctx,
5884                    state,
5885                    output,
5886                );
5887                None
5888            }
5889            Self::EnumDeclare(name, variants, span, type_params) => {
5890                debug_assert!(!uses_id);
5891                compile_enum_definition(name, variants, *span, type_params, ctx, state);
5892                None
5893            }
5894            Self::Match(scrutinee, arms, wildcard, span, true) => {
5895                debug_assert!(uses_id);
5896                let as_fn_value = matches!(self.infer_type(v, ctx, state), DataType::FnValue(_));
5897                Some(compile_match_value(
5898                    scrutinee,
5899                    arms,
5900                    wildcard.as_deref(),
5901                    *span,
5902                    v,
5903                    ctx,
5904                    state,
5905                    output,
5906                    tgt_id,
5907                    as_fn_value,
5908                ))
5909            }
5910            Self::Match(scrutinee, arms, wildcard, span, false) => {
5911                debug_assert!(!uses_id);
5912                compile_match(
5913                    scrutinee,
5914                    arms,
5915                    wildcard.as_deref(),
5916                    *span,
5917                    v,
5918                    ctx,
5919                    state,
5920                    output,
5921                );
5922                None
5923            }
5924            Self::NamespacedRef(path, span, type_args) => {
5925                debug_assert!(uses_id);
5926                if !type_args.is_empty() {
5927                    let (enum_id, variant_idx) =
5928                        resolve_generic_variant(path, type_args, *span, ctx, state);
5929                    return Some(compile_enum_construction(
5930                        enum_id,
5931                        variant_idx,
5932                        &[],
5933                        *span,
5934                        &[],
5935                        v,
5936                        ctx,
5937                        state,
5938                        output,
5939                    ));
5940                }
5941                if let Some((enum_id, variant_idx)) =
5942                    variant_constructor(path, &[], *span, v, ctx, state)
5943                {
5944                    Some(compile_enum_construction(
5945                        enum_id,
5946                        variant_idx,
5947                        &[],
5948                        *span,
5949                        &[],
5950                        v,
5951                        ctx,
5952                        state,
5953                        output,
5954                    ))
5955                } else {
5956                    compiler_errors::error_enum(
5957                        "Unknown enum variant",
5958                        &format!("{} does not name an enum variant", path.join("::")),
5959                        *span,
5960                        ctx.file_idx,
5961                        state.sources,
5962                    );
5963                }
5964            }
5965            Self::CallValue(callee, args, span, args_indexes) if !uses_id => {
5966                let output_id = handle_value_call(
5967                    output,
5968                    v,
5969                    ctx,
5970                    state,
5971                    tgt_id,
5972                    callee,
5973                    args,
5974                    *span,
5975                    args_indexes,
5976                );
5977                if let Some(id) = output_id {
5978                    state.free_reg(id, v);
5979                }
5980                None
5981            }
5982            Self::FunctionCall(args, namespace, markers, args_indexes, type_args) if !uses_id => {
5983                let output_id = handle_functions(
5984                    output,
5985                    v,
5986                    ctx,
5987                    state,
5988                    tgt_id,
5989                    args,
5990                    namespace,
5991                    *markers,
5992                    args_indexes,
5993                    type_args,
5994                );
5995                if let Some(id) = output_id {
5996                    state.free_reg(id, v);
5997                }
5998                None
5999            }
6000            Self::ObjFunctionCall(
6001                obj,
6002                args,
6003                namespace,
6004                obj_span,
6005                fn_span,
6006                args_indexes,
6007                type_args,
6008            ) if !uses_id => {
6009                let output_id = handle_method_calls(
6010                    output,
6011                    v,
6012                    ctx,
6013                    state,
6014                    tgt_id,
6015                    obj,
6016                    args,
6017                    namespace,
6018                    *obj_span,
6019                    *fn_span,
6020                    args_indexes,
6021                    type_args,
6022                );
6023                if let Some(id) = output_id {
6024                    state.free_reg(id, v);
6025                }
6026                None
6027            }
6028            Self::ObjFunctionCall(
6029                obj,
6030                args,
6031                namespace,
6032                obj_span,
6033                fn_span,
6034                args_indexes,
6035                type_args,
6036            ) if uses_id => Some(
6037                handle_method_calls(
6038                    output,
6039                    v,
6040                    ctx,
6041                    state,
6042                    tgt_id,
6043                    obj,
6044                    args,
6045                    namespace,
6046                    *obj_span,
6047                    *fn_span,
6048                    args_indexes,
6049                    type_args,
6050                )
6051                .unwrap_or_else(|| {
6052                    if let Some(&id) = state.const_registers.get(&NULL) {
6053                        id
6054                    } else {
6055                        let id = state.registers.len() as u16;
6056                        state.const_registers.insert(NULL, id);
6057                        state.registers.push(NULL);
6058                        id
6059                    }
6060                }),
6061            ),
6062            Self::FunctionDecl(fn_name, fn_args, fn_code, span, return_type, type_params) => {
6063                debug_assert!(!uses_id);
6064                compile_function_definition(
6065                    fn_name,
6066                    fn_args,
6067                    fn_code,
6068                    *span,
6069                    return_type.as_deref(),
6070                    type_params,
6071                    v,
6072                    ctx,
6073                    state,
6074                    output,
6075                );
6076                None
6077            }
6078            Self::ReturnVal(return_value) => {
6079                debug_assert!(!uses_id);
6080                compile_return(return_value.as_ref().as_ref(), v, ctx, state, output);
6081                None
6082            }
6083            Self::Break => {
6084                debug_assert!(!uses_id);
6085                compile_loop_break(ctx, output);
6086                None
6087            }
6088            Self::Continue => {
6089                debug_assert!(!uses_id);
6090                compile_loop_continue(ctx, output);
6091                None
6092            }
6093            Self::EvalBlock(code) => {
6094                debug_assert!(!uses_id);
6095                compile_eval_block(code, v, ctx, state, output);
6096                None
6097            }
6098            _ => unsafe { unreachable_unchecked() },
6099        }
6100    }
6101}
6102
6103#[cfg(target_arch = "aarch64")]
6104const ARCH_SUFFIX: &str = "-aarch64";
6105#[cfg(target_arch = "x86_64")]
6106const ARCH_SUFFIX: &str = "-x86_64";
6107#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
6108const ARCH_SUFFIX: &str = "";
6109
6110#[derive(Debug, Copy, Clone)]
6111pub enum SymbolKind {
6112    Fn(u16),
6113    Struct(u16),
6114    Enum(u16),
6115    /// A generic `struct` or `enum` declaration, by its index in
6116    /// `Generics::templates`. It names no type of its own: each set of type
6117    /// arguments it is applied to becomes an ordinary struct or enum. The
6118    /// symbol is what makes `g::Slot<int>` reach the `Slot` the module behind
6119    /// `g` declares rather than whichever module declared that name last.
6120    Template(u32),
6121}
6122
6123/// Whether two symbols are the same underlying definition (same kind, same
6124/// id in the global fn/struct/enum/template tables).
6125const fn symbol_ids_equal(a: SymbolKind, b: SymbolKind) -> bool {
6126    match (a, b) {
6127        (SymbolKind::Fn(x), SymbolKind::Fn(y))
6128        | (SymbolKind::Struct(x), SymbolKind::Struct(y))
6129        | (SymbolKind::Enum(x), SymbolKind::Enum(y)) => x == y,
6130        (SymbolKind::Template(x), SymbolKind::Template(y)) => x == y,
6131        _ => false,
6132    }
6133}
6134
6135#[derive(Debug, Clone, Default)]
6136pub struct Namespace {
6137    pub symbols: Vec<(SmolStr, SymbolKind)>,
6138    pub children: Vec<(SmolStr, Self)>,
6139    /// For a module bound by an import, the file it was read from, which is
6140    /// what makes two bindings of one name the same module or two different
6141    /// ones. Empty for a file's own scope and a `host` block.
6142    pub file: SmolStr,
6143    /// For a module bound by an import, its path as the import wrote it
6144    /// (`std/json`, `./geometry.cdl`), for a message to name it by.
6145    pub import_path: SmolStr,
6146}
6147
6148/// The scope of every file in the program, keyed by its index in `sources`.
6149///
6150/// A name written in a file resolves in that file's scope, whether it is
6151/// written in a signature or in a function body: the file's own declarations,
6152/// the symbols its bare imports merged in, and the modules it bound with `as`.
6153/// A module imported under an alias therefore still sees its own declarations
6154/// while its bodies compile, which the file that imported it does not.
6155///
6156/// File 0 is the entry file. Its scope is the program's outward surface: what
6157/// an embedding host can call and what the artifact exporter writes entry
6158/// points for.
6159#[derive(Debug, Clone, Default)]
6160pub struct FileNamespaces {
6161    files: FxHashMap<u16, Namespace>,
6162    /// Handed out for a file index no source was parsed for, so a lookup
6163    /// against one reports an unknown name instead of panicking.
6164    empty: Namespace,
6165    /// The files that are standard library modules, which are the only ones
6166    /// whose bodies reach the native primitives the library is written on.
6167    std_files: Vec<u16>,
6168}
6169
6170impl FileNamespaces {
6171    /// Records that `file_idx` is a standard library module.
6172    pub fn mark_std(&mut self, file_idx: u16) {
6173        self.std_files.push(file_idx);
6174    }
6175    /// Whether `file_idx` is a standard library module.
6176    #[must_use]
6177    pub fn is_std(&self, file_idx: u16) -> bool {
6178        self.std_files.contains(&file_idx)
6179    }
6180    pub fn insert(&mut self, file_idx: u16, namespace: Namespace) {
6181        self.files.insert(file_idx, namespace);
6182    }
6183    #[must_use]
6184    pub fn get(&self, file_idx: u16) -> &Namespace {
6185        self.files.get(&file_idx).unwrap_or(&self.empty)
6186    }
6187    pub fn get_mut(&mut self, file_idx: u16) -> &mut Namespace {
6188        self.files.entry(file_idx).or_default()
6189    }
6190    /// The entry file's scope.
6191    #[must_use]
6192    pub fn root(&self) -> &Namespace {
6193        self.get(0)
6194    }
6195    /// The symbol count of every file's scope, so a compile attempt that
6196    /// declares into one can be undone with [`FileNamespaces::rollback_to`].
6197    ///
6198    /// Every file is captured, not only the entry one: a compile writes into
6199    /// the scope of the file the body it is compiling was written in. A
6200    /// parameter inferred to be a function is declared in the scope the callee
6201    /// was written in, and a `let` bound to a function in the scope of the
6202    /// file the `let` sits in, so a call that reaches into an imported module
6203    /// grows that module's scope. Both declarations remove themselves once the
6204    /// body they belong to is compiled, which is why only an attempt that
6205    /// aborts partway leaves one behind.
6206    ///
6207    /// A namespace's children are not captured: one is pushed only where an
6208    /// import is parsed, and that happens before any of this state can be
6209    /// rolled back.
6210    #[must_use]
6211    pub fn checkpoint(&self) -> FileNamespacesCheckpoint {
6212        FileNamespacesCheckpoint {
6213            symbols: self
6214                .files
6215                .iter()
6216                .map(|(file_idx, namespace)| (*file_idx, namespace.symbols.len()))
6217                .collect(),
6218        }
6219    }
6220    /// Truncates every file's scope back to `checkpoint`.
6221    ///
6222    /// A file the checkpoint does not name had no symbols of its own to keep:
6223    /// a lookup against a file index nothing was parsed for creates its entry,
6224    /// empty, on demand.
6225    pub fn rollback_to(&mut self, checkpoint: &FileNamespacesCheckpoint) {
6226        for (file_idx, namespace) in &mut self.files {
6227            let len = checkpoint.symbols.get(file_idx).copied().unwrap_or(0);
6228            namespace.symbols.truncate(len);
6229        }
6230    }
6231}
6232
6233/// A checkpoint of the per-file scopes, taken by [`FileNamespaces::checkpoint`]
6234/// and undone by [`FileNamespaces::rollback_to`].
6235#[derive(Debug, Clone, Default)]
6236pub struct FileNamespacesCheckpoint {
6237    symbols: FxHashMap<u16, usize>,
6238}
6239
6240impl Namespace {
6241    pub fn fns(&self) -> impl Iterator<Item = &(SmolStr, SymbolKind)> {
6242        self.symbols
6243            .iter()
6244            .filter(|(_, kind)| matches!(kind, SymbolKind::Fn(_)))
6245    }
6246    pub fn structs(&self) -> impl Iterator<Item = &(SmolStr, SymbolKind)> {
6247        self.symbols
6248            .iter()
6249            .filter(|(_, kind)| matches!(kind, SymbolKind::Struct(_)))
6250    }
6251    /// Resolves a function id by name (with an optional module path). Returns
6252    /// `None` both when `path` names no namespace and when the namespace it
6253    /// names declares no such function; raising is left to the caller, which
6254    /// is the only place that knows whether another resolution can still
6255    /// match. A call into a `host` or `dylib` namespace is one that can: those
6256    /// functions are declared in `State::dyn_libs` and never appear in this
6257    /// tree.
6258    #[must_use]
6259    pub fn find_function(&self, path: &[SmolStr], function_name: &str) -> Option<usize> {
6260        self.resolve(path)?.symbols.iter().find_map(|(name, kind)| {
6261            if name.as_str() == function_name
6262                && let SymbolKind::Fn(fn_id) = kind
6263            {
6264                Some(*fn_id as usize)
6265            } else {
6266                None
6267            }
6268        })
6269    }
6270    #[must_use]
6271    pub fn find_struct(
6272        &self,
6273        path: &[SmolStr],
6274        struct_name: &str,
6275        span: Span,
6276        file_idx: u16,
6277        sources: &[Source],
6278    ) -> Option<usize> {
6279        self.walk_to_namespace(path, span, file_idx, sources)
6280            .symbols
6281            .iter()
6282            .find_map(|(name, kind)| {
6283                if name.as_str() == struct_name
6284                    && let SymbolKind::Struct(struct_id) = kind
6285                {
6286                    Some(*struct_id as usize)
6287                } else {
6288                    None
6289                }
6290            })
6291    }
6292    /// Resolves an enum type id by name (with an optional module path). Returns
6293    /// `None` when no enum by that name exists in the resolved namespace; never
6294    /// raises a compile error itself so it can be used for speculative
6295    /// enum-variant resolution against otherwise-unknown call/reference paths.
6296    #[must_use]
6297    pub fn find_enum(&self, path: &[SmolStr], enum_name: &str) -> Option<usize> {
6298        self.resolve(path)?.symbols.iter().find_map(|(name, kind)| {
6299            if name.as_str() == enum_name
6300                && let SymbolKind::Enum(enum_id) = kind
6301            {
6302                Some(*enum_id as usize)
6303            } else {
6304                None
6305            }
6306        })
6307    }
6308    /// Resolves a generic declaration by name (with an optional module path)
6309    /// to its index in `Generics::templates`. Returns `None` when the path
6310    /// names no namespace or the namespace it names declares no generic type
6311    /// by that name; the caller decides whether another resolution can still
6312    /// match.
6313    #[must_use]
6314    pub fn find_template(&self, path: &[SmolStr], type_name: &str) -> Option<usize> {
6315        self.resolve(path)?.symbols.iter().find_map(|(name, kind)| {
6316            if name.as_str() == type_name
6317                && let SymbolKind::Template(idx) = kind
6318            {
6319                Some(*idx as usize)
6320            } else {
6321                None
6322            }
6323        })
6324    }
6325    /// The namespace `path` names, or `None` when any segment of it is not
6326    /// declared. Resolution alone is not an error: a path can also name a
6327    /// `host` or `dylib` namespace, which lives in `State::dyn_libs`.
6328    #[must_use]
6329    pub fn resolve(&self, path: &[SmolStr]) -> Option<&Self> {
6330        let mut current = self;
6331        for sub in path {
6332            current = &current.children.iter().find(|(name, _)| name == sub)?.1;
6333        }
6334        Some(current)
6335    }
6336    /// The namespace `path` names, for a caller that has nowhere else to look:
6337    /// an unresolvable path ends the compile with the unknown-namespace error.
6338    #[must_use]
6339    pub fn walk_to_namespace(
6340        &self,
6341        path: &[SmolStr],
6342        span: Span,
6343        file_idx: u16,
6344        sources: &[Source],
6345    ) -> &Self {
6346        match self.resolve(path) {
6347            Some(namespace) => namespace,
6348            None => error_unknown_namespace(path, span, self, file_idx, sources),
6349        }
6350    }
6351}
6352
6353/// The key a loaded module is remembered under, so a module two files reach is
6354/// parsed once and its functions, structs and enums are registered once.
6355///
6356/// Two imports of one file can spell its path differently: a library or package
6357/// import resolves through the library directory (`import "strutil";`) while a
6358/// source-relative one resolves next to the importing file (`import
6359/// "../src/main.cdl";`), and either can arrive through a symbolic link or a
6360/// `..`. Resolving both to the same real path is what makes the two spellings
6361/// one module. A path with no real file behind it is kept as written, so the
6362/// error that follows names the place that was tried.
6363fn loaded_file_key(path: PathBuf) -> PathBuf {
6364    path.canonicalize().unwrap_or(path)
6365}
6366
6367/// The standard library modules every program has without an import.
6368///
6369/// They hold the methods of the builtin types and the `Option` and `Result`
6370/// enums, each named by the path a library import resolves (`std/list.cdl`).
6371/// They load after the program's own files, so a program that declares its own
6372/// `Option`, a variant called `Some` or a method of the same name finds its own
6373/// first. A method nothing calls is never compiled, so a program pays for the
6374/// ones it uses.
6375pub const PRELUDE_MODULES: [&str; 5] = [
6376    "std/option.cdl",
6377    "std/result.cdl",
6378    "std/list.cdl",
6379    "std/string.cdl",
6380    "std/map.cdl",
6381];
6382
6383/// The source of a standard library module the compiler carries, by the path
6384/// a library import resolves to (`std/list.cdl`).
6385///
6386/// Every build carries the prelude, so a program compiles the same with or
6387/// without a library directory beside the toolchain. The browser build also
6388/// carries the modules whose native half the runtime provides itself; see
6389/// [`candela_vm::intrinsics`].
6390fn embedded_std_module(path: &str) -> Option<&'static str> {
6391    match path {
6392        "std/option.cdl" => Some(include_str!("../../libs/std/option.cdl")),
6393        "std/result.cdl" => Some(include_str!("../../libs/std/result.cdl")),
6394        "std/list.cdl" => Some(include_str!("../../libs/std/list.cdl")),
6395        "std/string.cdl" => Some(include_str!("../../libs/std/string.cdl")),
6396        "std/map.cdl" => Some(include_str!("../../libs/std/map.cdl")),
6397        #[cfg(target_arch = "wasm32")]
6398        "std/math.cdl" => Some(include_str!("../../libs/std/math.cdl")),
6399        #[cfg(target_arch = "wasm32")]
6400        "std/time.cdl" => Some(include_str!("../../libs/std/time.cdl")),
6401        #[cfg(target_arch = "wasm32")]
6402        "std/random.cdl" => Some(include_str!("../../libs/std/random.cdl")),
6403        #[cfg(target_arch = "wasm32")]
6404        "std/hash.cdl" => Some(include_str!("../../libs/std/hash.cdl")),
6405        _ => None,
6406    }
6407}
6408
6409/// Where a library import reads from, and its text when the compiler carries
6410/// it rather than reading it from disk.
6411///
6412/// A module the compiler carries is always read from the compiler, keyed by its
6413/// library path, so the prelude and an explicit `import "std/option";` reach
6414/// one module, and the prelude a program gets is the one the compiler was
6415/// built with whatever library directory sits beside it. Anything else reads
6416/// from the library directory. `None` when neither has it.
6417fn library_module(
6418    path: &str,
6419    resolver: &ImportResolver,
6420) -> Option<(PathBuf, Option<&'static str>)> {
6421    if let Some(text) = embedded_std_module(path) {
6422        return Some((PathBuf::from(path), Some(text)));
6423    }
6424    #[cfg(not(target_arch = "wasm32"))]
6425    return resolver
6426        .library_path(path)
6427        .map(|on_disk| (loaded_file_key(on_disk), None));
6428    #[cfg(target_arch = "wasm32")]
6429    {
6430        let _ = resolver;
6431        None
6432    }
6433}
6434
6435/// The name a module the compiler carries is reported under: the file of that
6436/// name in the library directory when there is one, so a report and an
6437/// editor's go-to-definition point at a file that exists, and its library path
6438/// otherwise.
6439fn carried_module_filename(path: &str, resolver: &ImportResolver) -> SmolStr {
6440    #[cfg(not(target_arch = "wasm32"))]
6441    if let Some(on_disk) = resolver.library_path(path).filter(|p| p.is_file())
6442        && let Some(name) = loaded_file_key(on_disk).to_str()
6443    {
6444        return SmolStr::from(name);
6445    }
6446    #[cfg(target_arch = "wasm32")]
6447    let _ = resolver;
6448    SmolStr::from(path)
6449}
6450
6451/// The places an import was looked for, for the error that says it was found in
6452/// none of them.
6453///
6454/// `chosen` is where resolution settled and `library` is the library or package
6455/// path, which is the same file when the import is a library one and a second
6456/// place to name when it is a file import that fell back.
6457fn tried_paths(chosen: &Path, library: Option<&Path>) -> Vec<PathBuf> {
6458    let mut tried = vec![chosen.to_path_buf()];
6459    if let Some(library) = library
6460        && library != chosen
6461    {
6462        tried.push(library.to_path_buf());
6463    }
6464    tried
6465}
6466
6467/// Loads the [`PRELUDE_MODULES`], so their methods and enums need no import.
6468///
6469/// Each module is the compiler's own copy, registered under the key an import
6470/// of it resolves to, so a program that also imports one by name reaches the
6471/// same module. The modules declare impl blocks and generic enums, which every file
6472/// reaches by name, so nothing is merged into any file's scope.
6473fn load_prelude(
6474    fns: &mut Vec<Function>,
6475    structs: &mut Vec<Struct>,
6476    enums: &mut Vec<EnumType>,
6477    dynamic_libs: &mut Vec<Dynamiclib>,
6478    sources: &mut Vec<Source>,
6479    files: &mut FxHashMap<PathBuf, Namespace>,
6480    file_namespaces: &mut FileNamespaces,
6481    pending_structs: &mut Vec<(u16, u16, Box<[(SmolStr, TypeExpr, Span)]>)>,
6482    pending_enums: &mut PendingEnums,
6483    pending_fns: &mut PendingFns,
6484    pending_dylibs: &mut Vec<PendingDylib>,
6485    pending_host: &mut Vec<(
6486        u16,
6487        u16,
6488        Box<[(SmolStr, Box<[TypeExpr]>, TypeExpr, Span)]>,
6489        SmolStr,
6490        Span,
6491    )>,
6492    generics: &mut Generics,
6493    resolver: &ImportResolver,
6494) {
6495    for rel in PRELUDE_MODULES {
6496        let (Some((path, _)), Some(text)) =
6497            (library_module(rel, resolver), embedded_std_module(rel))
6498        else {
6499            continue;
6500        };
6501        if files.contains_key(&path) {
6502            continue;
6503        }
6504        let child_src_idx = sources.len() as u16;
6505        sources.push(Source {
6506            filename: carried_module_filename(rel, resolver),
6507            contents: String::from(text),
6508        });
6509        let parsed = parser::parse(&sources.last().unwrap().contents, sources.last().unwrap());
6510        generics.add_impls(parsed.impls, child_src_idx);
6511
6512        let mut child_namespace = Namespace::default();
6513        parse_toplevel(
6514            parsed.code,
6515            &path,
6516            child_src_idx,
6517            fns,
6518            structs,
6519            enums,
6520            dynamic_libs,
6521            sources,
6522            &mut child_namespace,
6523            files,
6524            file_namespaces,
6525            pending_structs,
6526            pending_enums,
6527            pending_fns,
6528            pending_dylibs,
6529            pending_host,
6530            generics,
6531            resolver,
6532        );
6533        file_namespaces.insert(child_src_idx, child_namespace);
6534        file_namespaces.mark_std(child_src_idx);
6535        generics.mark_prelude_file(child_src_idx);
6536    }
6537}
6538
6539/// Opens the library a bare logical import (`dylib "z"`) names. `filename` is
6540/// that name in this platform's convention (`resolve_library_filename`'s
6541/// output), and `dirs` are the directories it is looked for in, in order.
6542///
6543/// A bare filename handed to the OS loader (`dlopen` on Linux/macOS) is
6544/// resolved only through the system search path (the run-path,
6545/// `LD_LIBRARY_PATH`, `ld.so.cache`, `/lib`, `/usr/lib`), never the current
6546/// directory or the importing file's directory. Windows' `LoadLibraryA`
6547/// differs: it also searches the application directory and the current
6548/// directory by default. A library built to sit next to the `.cdl` file
6549/// (rather than installed as a system library) therefore loads on Windows but
6550/// silently fails on Linux/macOS.
6551///
6552/// To match Windows' default search order, the name is tried, in order: under
6553/// each directory in `dirs`, then in the current directory, and only then
6554/// handed to the OS loader bare, so genuine system libraries (`z`, `m`,
6555/// `sqlite3`) still resolve exactly as before.
6556#[cfg(not(target_arch = "wasm32"))]
6557fn open_logical_dylib(dirs: &[&Path], filename: &str) -> Option<Library> {
6558    let mut candidates: Vec<PathBuf> = Vec::with_capacity(dirs.len() + 1);
6559    for dir in dirs {
6560        let candidate = dir.join(filename);
6561        if !candidates.contains(&candidate) {
6562            candidates.push(candidate);
6563        }
6564    }
6565    let cwd_candidate = Path::new(".").join(filename);
6566    if !candidates.contains(&cwd_candidate) {
6567        candidates.push(cwd_candidate);
6568    }
6569    for candidate in &candidates {
6570        if let Ok(lib) = unsafe { Library::new(candidate) } {
6571            return Some(lib);
6572        }
6573    }
6574    unsafe { Library::new(filename) }.ok()
6575}
6576
6577/// Whether a value of type `ty` has a C shape a `dylib` call can pass or
6578/// return: an `int`, a `float`, a `string`, an array, `null` for a function
6579/// that returns nothing, or a struct whose fields all have one. `depth` stops a
6580/// struct that holds itself from recursing without end.
6581fn has_c_representation(ty: &DataType, structs: &[Struct], depth: usize) -> bool {
6582    match ty {
6583        DataType::Int
6584        | DataType::Float
6585        | DataType::String
6586        | DataType::Array(_)
6587        | DataType::Null => true,
6588        DataType::Struct(id) => {
6589            depth < structs.len()
6590                && structs.get(*id as usize).is_some_and(|s| {
6591                    s.fields
6592                        .iter()
6593                        .all(|(_, field, _)| has_c_representation(field, structs, depth + 1))
6594                })
6595        }
6596        _ => false,
6597    }
6598}
6599
6600/// The namespace a `dylib` block's functions live under: a logical name as
6601/// written (`z`), and for a path the file's name without its directory or
6602/// extension (`../native/mylib` is `mylib`).
6603#[cfg(not(target_arch = "wasm32"))]
6604fn dylib_namespace(spec: &str, is_logical: bool) -> SmolStr {
6605    if is_logical {
6606        return SmolStr::from(spec);
6607    }
6608    Path::new(spec)
6609        .file_prefix()
6610        .and_then(|s| s.to_str())
6611        .unwrap_or(spec)
6612        .to_smolstr()
6613}
6614
6615/// Opens the library a path import (`dylib "../native/mylib"`) names, and
6616/// returns the namespace name its functions live under.
6617///
6618/// A relative path is tried under each directory in `dirs`, in order; an
6619/// absolute one goes straight to the loader. The namespace comes from the last
6620/// component of the path either way, so where the file was found does not
6621/// change how the program calls into it.
6622#[cfg(not(target_arch = "wasm32"))]
6623fn open_path_dylib(dirs: &[&Path], spec: &str) -> (Option<Library>, SmolStr) {
6624    let name = dylib_namespace(spec, false);
6625
6626    if Path::new(spec).is_absolute() {
6627        return (open_library_path(spec), name);
6628    }
6629    for dir in dirs {
6630        if let Some(lib) = open_library_path(dir.join(spec).to_string_lossy().as_ref()) {
6631            return (Some(lib), name);
6632        }
6633    }
6634    (None, name)
6635}
6636
6637/// The spec to record for a library the standard library names, relative to the
6638/// `libs` directory, and `None` for every other `dylib` import.
6639///
6640/// A std module reaches its C library by a path from its own source file
6641/// (`../std_src/math/math`), and an artifact carries no source tree to resolve
6642/// that against. Recording the path from `libs` instead is what lets
6643/// `candela-vm` find the file through the toolchain. Both ends have to sit
6644/// under the library directory: the module doing the import, and the library it
6645/// names.
6646#[cfg(not(target_arch = "wasm32"))]
6647fn std_library_spec(lib_dir: Option<&Path>, file_path: &Path, spec: &str) -> Option<SmolStr> {
6648    let lib_dir = lib_dir?.canonicalize().ok()?;
6649    if !file_path.canonicalize().ok()?.starts_with(&lib_dir) {
6650        return None;
6651    }
6652    let target = file_path
6653        .parent()
6654        .unwrap_or_else(|| Path::new("."))
6655        .join(spec);
6656    // The spec leaves the platform extension off, so the file is not there to
6657    // canonicalize; its directory is.
6658    let relative = target
6659        .parent()?
6660        .canonicalize()
6661        .ok()?
6662        .strip_prefix(&lib_dir)
6663        .ok()?
6664        .join(target.file_name()?);
6665    // Written with forward slashes, so an artifact built on Windows names the
6666    // same file on Linux.
6667    let mut recorded = String::new();
6668    for part in relative.components() {
6669        if !recorded.is_empty() {
6670            recorded.push('/');
6671        }
6672        recorded.push_str(part.as_os_str().to_str()?);
6673    }
6674    Some(SmolStr::from(recorded))
6675}
6676
6677/// Opens the library file `base` stands for.
6678///
6679/// A path that already carries an extension is used as written. One without
6680/// picks up an architecture-specific build when it sits beside it
6681/// (`mylib-x86_64.so`), so a single directory can hold builds for several
6682/// architectures, and otherwise gets this platform's extension.
6683#[cfg(not(target_arch = "wasm32"))]
6684fn open_library_path(base: &str) -> Option<Library> {
6685    let resolved = if Path::new(base).extension().is_none() {
6686        let arch_path = format!(
6687            "{base}{ARCH_SUFFIX}.{}",
6688            TargetOs::CURRENT.dynamic_lib_extension()
6689        );
6690        if Path::new(&arch_path).exists() {
6691            arch_path
6692        } else {
6693            resolve_library_filename(base, TargetOs::CURRENT)
6694        }
6695    } else {
6696        base.to_owned()
6697    };
6698    unsafe { Library::new(&resolved) }.ok()
6699}
6700
6701/// Recursively collects functions, dyn libs, and imported files
6702/// Deferred enum-payload resolution: `(enum_id, src_file_idx, variants)`, filled
6703/// in `resolve_types` after every type name is registered, so an enum payload
6704/// may reference a type declared later.
6705type PendingEnums = Vec<(u16, u16, Box<[(SmolStr, Box<[TypeExpr]>, Span)]>)>;
6706/// Deferred function-signature resolution: `(fn_id, src_file_idx, args,
6707/// return_type, type_params)`, resolved in `resolve_types` once every type name
6708/// is registered.
6709type PendingFns = Vec<(
6710    u16,
6711    u16,
6712    Box<[(SmolStr, Option<TypeExpr>)]>,
6713    ReturnAnnotation,
6714    TypeParams,
6715)>;
6716
6717/// Declares everything `code` defines into `namespace`, parsing each file it
6718/// imports the same way.
6719///
6720/// Registering the finished scope in `file_namespaces` is the caller's: an
6721/// imported file's scope is bound into the importer as well, while the entry
6722/// file's is needed nowhere else and can be handed over whole.
6723/// Registers the structs a `host "ns" { ... }` block declares. Each is named
6724/// behind the block's namespace, `ns::Name`, the way the block's functions are
6725/// called, and its type is registered under that name.
6726fn declare_host_structs(
6727    host_namespace: &SmolStr,
6728    host_structs: Box<[Expr]>,
6729    src_file_idx: u16,
6730    structs: &mut Vec<Struct>,
6731    namespace: &mut Namespace,
6732    pending_structs: &mut Vec<(u16, u16, Box<[(SmolStr, TypeExpr, Span)]>)>,
6733    generics: &mut Generics,
6734) {
6735    if host_structs.is_empty() {
6736        return;
6737    }
6738    let local: Vec<SmolStr> = host_structs
6739        .iter()
6740        .filter_map(|decl| match decl {
6741            Expr::StructDeclare(name, ..) => Some(name.clone()),
6742            _ => None,
6743        })
6744        .collect();
6745    let child = if let Some(idx) = namespace
6746        .children
6747        .iter()
6748        .position(|(name, _)| name == host_namespace)
6749    {
6750        idx
6751    } else {
6752        namespace
6753            .children
6754            .push((host_namespace.clone(), Namespace::default()));
6755        namespace.children.len() - 1
6756    };
6757    for decl in host_structs {
6758        let Expr::StructDeclare(name, fields, span, _, defaults) = decl else {
6759            continue;
6760        };
6761        let struct_id = structs.len() as u16;
6762        structs.push(Struct {
6763            name: format_args!("{host_namespace}::{name}").to_smolstr(),
6764            fields: Box::from([]),
6765            id: struct_id,
6766            name_span: span,
6767        });
6768        generics.set_struct_defaults(struct_id, src_file_idx, defaults);
6769        namespace.children[child]
6770            .1
6771            .symbols
6772            .push((name, SymbolKind::Struct(struct_id)));
6773        let fields = fields
6774            .iter()
6775            .map(|(field, field_type, field_span)| {
6776                (
6777                    field.clone(),
6778                    qualify_host_type(field_type, host_namespace, &local),
6779                    *field_span,
6780                )
6781            })
6782            .collect();
6783        pending_structs.push((struct_id, src_file_idx, fields));
6784    }
6785}
6786
6787/// `t` with every bare name of a struct the same `host` block declares written
6788/// behind the block's namespace, which is where the struct is registered. A
6789/// block names its own structs bare: `bool start(StartOptions);`.
6790fn qualify_host_type(t: &TypeExpr, host_namespace: &SmolStr, local: &[SmolStr]) -> TypeExpr {
6791    let qualify = |inner: &TypeExpr| qualify_host_type(inner, host_namespace, local);
6792    match t {
6793        TypeExpr::Identifier(name, span) if local.contains(name) => {
6794            TypeExpr::NamespacedIdentifier(Box::from([host_namespace.clone(), name.clone()]), *span)
6795        }
6796        TypeExpr::Array(inner) => TypeExpr::Array(Box::new(qualify(inner))),
6797        TypeExpr::Map(key, value) => {
6798            TypeExpr::Map(Box::new(qualify(key)), Box::new(qualify(value)))
6799        }
6800        TypeExpr::Union(members) => TypeExpr::Union(members.iter().map(qualify).collect()),
6801        TypeExpr::Fn(f) => TypeExpr::Fn(Box::new(FnTypeExpr {
6802            args: f.args.iter().map(qualify).collect(),
6803            return_type: f.return_type.as_ref().map(qualify),
6804            span: f.span,
6805        })),
6806        other => other.clone(),
6807    }
6808}
6809
6810/// Adds what one import brings in to the scope of the file that writes it.
6811///
6812/// `exports` is what the module declares. A namespace binding puts it behind
6813/// its name; a list of items merges each named one into the file's own scope,
6814/// with the `impl` methods of a type it names. A name already bound to a
6815/// different module, an item the module does not declare, and an item that
6816/// clashes with one the file already has are compile errors.
6817fn bind_import(
6818    namespace: &mut Namespace,
6819    exports: Namespace,
6820    binding: ImportBinding,
6821    file_path: &Path,
6822    module_display: &SmolStr,
6823    merged_symbol_origins: &mut Vec<(SmolStr, SmolStr)>,
6824    span: Span,
6825    src_file_idx: u16,
6826    sources: &[Source],
6827) {
6828    let file: SmolStr = file_path.to_string_lossy().as_ref().into();
6829    match binding {
6830        ImportBinding::Namespace(name) => {
6831            if let Some((_, existing)) = namespace.children.iter().find(|(n, _)| *n == name) {
6832                // The same module bound twice under one name is one binding.
6833                if existing.file == file {
6834                    return;
6835                }
6836                compiler_errors::error_import_name_collision(
6837                    &name,
6838                    &existing.import_path,
6839                    module_display,
6840                    span,
6841                    src_file_idx,
6842                    sources,
6843                );
6844            }
6845            namespace.children.push((
6846                name,
6847                Namespace {
6848                    file,
6849                    import_path: module_display.clone(),
6850                    ..exports
6851                },
6852            ));
6853        }
6854        ImportBinding::Items(items) => {
6855            for (item, item_span) in items {
6856                let found: Vec<(SmolStr, SymbolKind)> = exports
6857                    .symbols
6858                    .iter()
6859                    .filter(|(name, kind)| {
6860                        *name == item && !(name == "main" && matches!(kind, SymbolKind::Fn(_)))
6861                    })
6862                    .cloned()
6863                    .collect();
6864                if found.is_empty() {
6865                    compiler_errors::error_import_item_not_exported(
6866                        &item,
6867                        module_display,
6868                        &exported_names(&exports),
6869                        item_span,
6870                        src_file_idx,
6871                        sources,
6872                    );
6873                }
6874                // A type brings the functions its `impl` blocks declare.
6875                let methods = exports.symbols.iter().filter(|(name, kind)| {
6876                    matches!(kind, SymbolKind::Fn(_))
6877                        && name
6878                            .split_once(METHOD_SEP)
6879                            .is_some_and(|(type_name, _)| type_name == item)
6880                });
6881                let methods: Vec<(SmolStr, SymbolKind)> = if found
6882                    .iter()
6883                    .any(|(_, kind)| !matches!(kind, SymbolKind::Fn(_)))
6884                {
6885                    methods.cloned().collect()
6886                } else {
6887                    Vec::new()
6888                };
6889                for (name, kind) in found.into_iter().chain(methods) {
6890                    if let Some((_, existing)) = namespace.symbols.iter().find(|(n, _)| *n == name)
6891                    {
6892                        // The same underlying symbol arriving through two
6893                        // routes is not a conflict.
6894                        if symbol_ids_equal(*existing, kind) {
6895                            continue;
6896                        }
6897                        let existing_origin = merged_symbol_origins
6898                            .iter()
6899                            .find(|(n, _)| *n == name)
6900                            .map_or_else(
6901                                || String::from("defined in this file"),
6902                                |(_, module)| format!("imported from \"{module}\""),
6903                            );
6904                        compiler_errors::error_import_symbol_collision(
6905                            &name,
6906                            &existing_origin,
6907                            module_display,
6908                            item_span,
6909                            src_file_idx,
6910                            sources,
6911                        );
6912                    }
6913                    merged_symbol_origins.push((name.clone(), module_display.clone()));
6914                    namespace.symbols.push((name, kind));
6915                }
6916            }
6917        }
6918    }
6919}
6920
6921/// The names a module declares that an import can name, in declaration
6922/// order: its functions, structs and enums, without the `impl` methods that
6923/// travel with a type and without a `main` of its own.
6924fn exported_names(exports: &Namespace) -> Vec<SmolStr> {
6925    let mut names: Vec<SmolStr> = Vec::new();
6926    for (name, kind) in &exports.symbols {
6927        if name.contains(METHOD_SEP)
6928            || name.starts_with(type_system::ANON_FN_PREFIX)
6929            || (name == "main" && matches!(kind, SymbolKind::Fn(_)))
6930            || names.contains(name)
6931        {
6932            continue;
6933        }
6934        names.push(name.clone());
6935    }
6936    names
6937}
6938
6939fn parse_toplevel(
6940    code: Vec<Expr>,
6941    file_path: &Path,
6942    src_file_idx: u16,
6943    fns: &mut Vec<Function>,
6944    structs: &mut Vec<Struct>,
6945    enums: &mut Vec<EnumType>,
6946    dynamic_libs: &mut Vec<Dynamiclib>,
6947    sources: &mut Vec<Source>,
6948    namespace: &mut Namespace,
6949    files: &mut FxHashMap<PathBuf, Namespace>,
6950    file_namespaces: &mut FileNamespaces,
6951    pending_structs: &mut Vec<(u16, u16, Box<[(SmolStr, TypeExpr, Span)]>)>,
6952    pending_enums: &mut PendingEnums,
6953    pending_fns: &mut PendingFns,
6954    pending_dylibs: &mut Vec<PendingDylib>,
6955    pending_host: &mut Vec<(
6956        u16,
6957        u16,
6958        Box<[(SmolStr, Box<[TypeExpr]>, TypeExpr, Span)]>,
6959        SmolStr,
6960        Span,
6961    )>,
6962    generics: &mut Generics,
6963    resolver: &ImportResolver,
6964) {
6965    let mut imports = Vec::new();
6966    for expr in code {
6967        match expr {
6968            Expr::FunctionDecl(fn_name, fn_args, fn_code, span, fn_return_type, type_params) => {
6969                if let Some((_, SymbolKind::Fn(func_id))) =
6970                    namespace.symbols.iter().rfind(|(f, _)| f == &fn_name)
6971                {
6972                    let func = &fns[*func_id as usize];
6973                    compiler_errors::error_function_already_defined(
6974                        func,
6975                        span,
6976                        src_file_idx,
6977                        sources,
6978                    );
6979                }
6980                check_operator_method(&fn_name, &fn_args, span, src_file_idx, sources);
6981                let returns_void = check_if_returns_void(&fn_code);
6982                let mut callees = Vec::new();
6983                collect_direct_fn_calls(&fn_code, self_type_of(&fn_name), &mut callees);
6984
6985                let fn_id = fns.len() as u16;
6986                fns.push(Function {
6987                    name: fn_name.clone(),
6988                    args: Box::new([]),
6989                    code: fn_code,
6990                    impls: Vec::new(),
6991                    is_recursive: None,
6992                    returns_null: returns_void,
6993                    src_file: src_file_idx,
6994                    return_type_cache: Vec::new(),
6995                    direct_calls: callees.into_boxed_slice(),
6996                    name_span: span,
6997                    // Resolved with the argument types once every file's
6998                    // namespace is known; see the `pending_fns` drain.
6999                    return_type: None,
7000                    captures: Box::from([]),
7001                    generics: (!type_params.is_empty()).then(|| {
7002                        Box::new(FnGenerics {
7003                            params: type_params.clone(),
7004                            arg_types: fn_args.iter().map(|(_, t)| t.clone()).collect(),
7005                            return_type: fn_return_type.clone(),
7006                            bindings: Box::from([]),
7007                            file_idx: src_file_idx,
7008                        })
7009                    }),
7010                    entry_register: None,
7011                });
7012                pending_fns.push((fn_id, src_file_idx, fn_args, fn_return_type, type_params));
7013                namespace.symbols.push((fn_name, SymbolKind::Fn(fn_id)));
7014            }
7015            Expr::StructDeclare(name, fields, span, type_params, defaults) => {
7016                // A generic declaration registers no type of its own: each
7017                // instantiation of it becomes an ordinary struct.
7018                if !type_params.is_empty() {
7019                    let template = generics.add_struct_template(
7020                        name.clone(),
7021                        type_params,
7022                        src_file_idx,
7023                        fields,
7024                        defaults,
7025                    );
7026                    namespace
7027                        .symbols
7028                        .push((name, SymbolKind::Template(template)));
7029                    continue;
7030                }
7031                let struct_id = structs.len() as u16;
7032                structs.push(Struct {
7033                    name: name.clone(),
7034                    fields: Box::from([]),
7035                    id: struct_id,
7036                    name_span: span,
7037                });
7038                generics.set_struct_defaults(struct_id, src_file_idx, defaults);
7039                namespace
7040                    .symbols
7041                    .push((name, SymbolKind::Struct(struct_id)));
7042                pending_structs.push((struct_id, src_file_idx, fields));
7043            }
7044            Expr::HostBlock(host_namespace, fn_signatures, span, host_structs) => {
7045                declare_host_structs(
7046                    &host_namespace,
7047                    host_structs,
7048                    src_file_idx,
7049                    structs,
7050                    namespace,
7051                    pending_structs,
7052                    generics,
7053                );
7054                let local: Vec<SmolStr> = namespace
7055                    .children
7056                    .iter()
7057                    .find(|(child, _)| *child == host_namespace)
7058                    .map(|(_, ns)| ns.structs().map(|(n, _)| n.clone()).collect())
7059                    .unwrap_or_default();
7060                let fn_signatures = if local.is_empty() {
7061                    fn_signatures
7062                } else {
7063                    fn_signatures
7064                        .iter()
7065                        .map(|(fn_name, args, ret, fn_span)| {
7066                            (
7067                                fn_name.clone(),
7068                                args.iter()
7069                                    .map(|t| qualify_host_type(t, &host_namespace, &local))
7070                                    .collect(),
7071                                qualify_host_type(ret, &host_namespace, &local),
7072                                *fn_span,
7073                            )
7074                        })
7075                        .collect()
7076                };
7077                imports.push(Expr::HostBlock(
7078                    host_namespace,
7079                    fn_signatures,
7080                    span,
7081                    Box::from([]),
7082                ));
7083            }
7084            Expr::EnumDeclare(name, variants, span, type_params) => {
7085                if !type_params.is_empty() {
7086                    let template = generics.add_enum_template(
7087                        name.clone(),
7088                        type_params,
7089                        src_file_idx,
7090                        variants,
7091                    );
7092                    namespace
7093                        .symbols
7094                        .push((name, SymbolKind::Template(template)));
7095                    continue;
7096                }
7097                let enum_id = enums.len() as u16;
7098                enums.push(EnumType {
7099                    name: name.clone(),
7100                    variants: Box::from([]),
7101                    id: enum_id,
7102                    name_span: span,
7103                });
7104                namespace.symbols.push((name, SymbolKind::Enum(enum_id)));
7105                pending_enums.push((enum_id, src_file_idx, variants));
7106            }
7107            // A browser has no file system and no library loader. The std
7108            // modules the runtime carries import, and their own `dylib` blocks
7109            // bind to its intrinsics; nothing else does.
7110            #[cfg(target_arch = "wasm32")]
7111            Expr::ImportDylib(..) if file_path.to_str().and_then(embedded_std_module).is_none() => {
7112                wasm_error("WASM does not support loading dynamic libraries")
7113            }
7114            #[cfg(target_arch = "wasm32")]
7115            Expr::ImportFile(ref path, _, is_logical, _)
7116                if !is_logical || embedded_std_module(path).is_none() =>
7117            {
7118                wasm_error(
7119                    "WASM does not support importing files. The standard library modules std/math, std/time, std/random and std/hash import",
7120                )
7121            }
7122            import @ (Expr::ImportFile(..) | Expr::ImportDylib(..)) => {
7123                imports.push(import);
7124            }
7125            _ => {}
7126        }
7127    }
7128
7129    files.insert(file_path.to_path_buf(), namespace.clone());
7130
7131    // Names merged into this file's scope by bare imports, with the module
7132    // each came from; consulted to report both sources on a collision.
7133    let mut merged_symbol_origins: Vec<(SmolStr, SmolStr)> = Vec::new();
7134
7135    for import in imports {
7136        match import {
7137            #[cfg(not(target_arch = "wasm32"))]
7138            Expr::ImportDylib(path, fn_signatures, span) => {
7139                // The spec exactly as written; recorded in the artifact recipe so
7140                // a `.cdlb` re-resolves the library by name (per-OS) at load.
7141                let spec = path.clone();
7142                // A bare logical name (no path separator, not absolute, no
7143                // extension, e.g. `z`, `sqlite3`) names a system library the
7144                // OS loader searches for. Anything with a separator or extension
7145                // is an explicit path.
7146                let is_logical = !spec.contains('/')
7147                    && !spec.contains('\\')
7148                    && !Path::new(spec.as_str()).is_absolute()
7149                    && Path::new(spec.as_str()).extension().is_none();
7150
7151                // Where a relative library reference is looked for, in order:
7152                // the directories the embedding host named, the root of every
7153                // package this program depends on, then the importing file's
7154                // own. A host whose libraries sit apart from its sources
7155                // (`lib/` beside `src/`) names that directory and both forms
7156                // find it; a package that ships a native library beside its
7157                // `.cdl` sources is found the same way.
7158                let host_dirs = crate::rt::dylib_dirs();
7159                let package_dirs = resolver.root_dirs();
7160                let file_dir = file_path.parent().unwrap_or_else(|| Path::new("."));
7161                let mut dirs: Vec<&Path> = Vec::with_capacity(host_dirs.len() + 2);
7162                dirs.extend(host_dirs.iter().map(PathBuf::as_path));
7163                dirs.extend(package_dirs.iter().map(PathBuf::as_path));
7164                dirs.push(file_dir);
7165
7166                // A check-only compile binds the declared signatures and opens
7167                // nothing, so a library that is not there yet is no error.
7168                let (lib, dylib_name) = if is_check_only() {
7169                    (None, dylib_namespace(spec.as_str(), is_logical))
7170                } else {
7171                    let (lib, dylib_name) = if is_logical {
7172                        // e.g. `z` -> `libz.so` / `libz.dylib` / `z.dll`.
7173                        let filename = resolve_library_filename(spec.as_str(), TargetOs::CURRENT);
7174                        (open_logical_dylib(&dirs, &filename), spec.clone())
7175                    } else {
7176                        open_path_dylib(&dirs, spec.as_str())
7177                    };
7178                    let lib = lib
7179                        .unwrap_or_else(|| error_cannot_load_dynlib(span, src_file_idx, sources));
7180                    (Some(Rc::new(lib)), dylib_name)
7181                };
7182                // A standard-library module names its C library by a path
7183                // relative to its own source file, which an artifact never
7184                // carries. Record that one relative to the `libs` directory
7185                // instead, so `candela-vm` reaches it through the toolchain
7186                // from whatever directory the artifact runs in.
7187                let recorded = match std_library_spec(resolver.lib_dir(), file_path, spec.as_str())
7188                {
7189                    Some(relative) => (relative, LibraryOrigin::StandardLibrary),
7190                    None => (spec, LibraryOrigin::Program),
7191                };
7192                pending_dylibs.push((
7193                    src_file_idx,
7194                    dynamic_libs.len() as u16,
7195                    fn_signatures,
7196                    lib,
7197                    recorded,
7198                    span,
7199                ));
7200                dynamic_libs.push(Dynamiclib {
7201                    name: dylib_name,
7202                    fns: Box::new([]),
7203                    is_host: false,
7204                });
7205            }
7206            // Only a std module's block gets here (see the first pass), and it
7207            // names its library relative to its own file, as on every target.
7208            // The binding is recorded the way a desktop build records it, so
7209            // an intrinsic resolves it by the same name.
7210            #[cfg(target_arch = "wasm32")]
7211            Expr::ImportDylib(path, fn_signatures, span) => {
7212                let recorded = path.strip_prefix("../").unwrap_or(path.as_str());
7213                let dylib_name = Path::new(recorded)
7214                    .file_prefix()
7215                    .and_then(|s| s.to_str())
7216                    .unwrap_or(recorded)
7217                    .to_smolstr();
7218                pending_dylibs.push((
7219                    src_file_idx,
7220                    dynamic_libs.len() as u16,
7221                    fn_signatures,
7222                    (),
7223                    (SmolStr::from(recorded), LibraryOrigin::StandardLibrary),
7224                    span,
7225                ));
7226                dynamic_libs.push(Dynamiclib {
7227                    name: dylib_name,
7228                    fns: Box::new([]),
7229                    is_host: false,
7230                });
7231            }
7232            Expr::HostBlock(host_namespace, fn_signatures, span, _) => {
7233                pending_host.push((
7234                    src_file_idx,
7235                    dynamic_libs.len() as u16,
7236                    fn_signatures,
7237                    host_namespace.clone(),
7238                    span,
7239                ));
7240                dynamic_libs.push(Dynamiclib {
7241                    name: host_namespace,
7242                    fns: Box::new([]),
7243                    is_host: true,
7244                });
7245            }
7246            Expr::ImportFile(path, binding, is_logical, span) => {
7247                // Where the import reads from, and, for the error when it reads
7248                // from nowhere, every place that was tried. A module the
7249                // compiler carries comes with its text.
7250                let library = library_module(path.as_str(), resolver);
7251                #[cfg(not(target_arch = "wasm32"))]
7252                let library_path = library.as_ref().map(|(path, _)| path.clone());
7253                // In a browser the module is one the runtime carries; the first
7254                // pass let no other through.
7255                #[cfg(target_arch = "wasm32")]
7256                let file_path = library
7257                    .as_ref()
7258                    .map_or_else(|| PathBuf::from(path.as_str()), |(path, _)| path.clone());
7259                #[cfg(not(target_arch = "wasm32"))]
7260                let file_path = if is_logical {
7261                    // A library import (`import "std/string";`, extensionless)
7262                    // resolves against a package root or the shipped library
7263                    // directory, never source-relative, so it works from any
7264                    // working directory with nothing set.
7265                    library_path.clone().unwrap_or_else(|| {
7266                        error_cannot_read_file(span, src_file_idx, sources, &[]);
7267                    })
7268                } else {
7269                    // A `.cdl` file import resolves next to the importing file first,
7270                    // then falls back to the shipped library directory.
7271                    let beside = file_path
7272                        .parent()
7273                        .unwrap_or_else(|| Path::new("."))
7274                        .join(path.as_str());
7275                    beside.canonicalize().unwrap_or_else(|_| {
7276                        library_path.clone().unwrap_or_else(|| {
7277                            error_cannot_read_file(
7278                                span,
7279                                src_file_idx,
7280                                sources,
7281                                std::slice::from_ref(&beside),
7282                            );
7283                        })
7284                    })
7285                };
7286
7287                let child_namespace = if let Some(cached) = files.get(&file_path) {
7288                    cached.clone()
7289                } else {
7290                    let carried = library
7291                        .as_ref()
7292                        .filter(|(path, _)| *path == file_path)
7293                        .and_then(|(_, text)| *text);
7294                    let file_contents = match carried {
7295                        Some(text) => String::from(text),
7296                        #[cfg(target_arch = "wasm32")]
7297                        None => String::new(),
7298                        #[cfg(not(target_arch = "wasm32"))]
7299                        None => std::fs::read_to_string(&file_path).unwrap_or_else(|_| {
7300                            error_cannot_read_file(
7301                                span,
7302                                src_file_idx,
7303                                sources,
7304                                &tried_paths(&file_path, library_path.as_deref()),
7305                            );
7306                        }),
7307                    };
7308                    let file_name: SmolStr = if carried.is_some() {
7309                        carried_module_filename(path.as_str(), resolver)
7310                    } else {
7311                        file_path.to_str().unwrap_or(path.as_str()).into()
7312                    };
7313
7314                    let child_src_idx = sources.len() as u16;
7315
7316                    sources.push(Source {
7317                        filename: file_name.clone(),
7318                        contents: file_contents,
7319                    });
7320
7321                    // Parse the imported file's contents
7322                    let parsed =
7323                        parser::parse(&sources.last().unwrap().contents, sources.last().unwrap());
7324                    generics.add_impls(parsed.impls, child_src_idx);
7325
7326                    let mut child_namespace = Namespace::default();
7327
7328                    parse_toplevel(
7329                        parsed.code,
7330                        &file_path,
7331                        child_src_idx,
7332                        fns,
7333                        structs,
7334                        enums,
7335                        dynamic_libs,
7336                        sources,
7337                        &mut child_namespace,
7338                        files,
7339                        file_namespaces,
7340                        pending_structs,
7341                        pending_enums,
7342                        pending_fns,
7343                        pending_dylibs,
7344                        pending_host,
7345                        generics,
7346                        resolver,
7347                    );
7348                    file_namespaces.insert(child_src_idx, child_namespace.clone());
7349                    if is_logical && path.starts_with("std/") {
7350                        file_namespaces.mark_std(child_src_idx);
7351                    }
7352                    if is_logical && PRELUDE_MODULES.contains(&path.as_str()) {
7353                        generics.mark_prelude_file(child_src_idx);
7354                    }
7355                    // What an importer sees is what the module declares, the
7356                    // snapshot taken before its own imports were bound; its
7357                    // full scope, imports included, is its alone.
7358                    files.get(&file_path).cloned().unwrap_or_default()
7359                };
7360                // The module path as written, to name the source in a message.
7361                let module_display: SmolStr = if is_logical {
7362                    path.strip_suffix(".cdl").unwrap_or(path.as_str()).into()
7363                } else {
7364                    path.clone()
7365                };
7366                bind_import(
7367                    namespace,
7368                    child_namespace,
7369                    binding,
7370                    &file_path,
7371                    &module_display,
7372                    &mut merged_symbol_origins,
7373                    span,
7374                    src_file_idx,
7375                    sources,
7376                );
7377            }
7378            _ => unsafe { unreachable_unchecked() },
7379        }
7380    }
7381
7382    // The program's own declarations, and the modules it imports, are
7383    // registered first, so a program or a package that declares a method or a
7384    // variant the prelude also declares keeps its own; see
7385    // `Generics::prelude_templates`.
7386    if src_file_idx == 0 {
7387        let first_template = generics.template_count();
7388        load_prelude(
7389            fns,
7390            structs,
7391            enums,
7392            dynamic_libs,
7393            sources,
7394            files,
7395            file_namespaces,
7396            pending_structs,
7397            pending_enums,
7398            pending_fns,
7399            pending_dylibs,
7400            pending_host,
7401            generics,
7402            resolver,
7403        );
7404        generics.set_prelude_templates(first_template..generics.template_count());
7405    }
7406}
7407
7408fn resolve_types(
7409    structs: &mut Vec<Struct>,
7410    enums: &mut Vec<EnumType>,
7411    fns: &mut Vec<Function>,
7412    generics: &mut Generics,
7413    pending_structs: Vec<(u16, u16, Box<[(SmolStr, TypeExpr, Span)]>)>,
7414    pending_enums: PendingEnums,
7415    pending_fns: PendingFns,
7416    pending_dylibs: Vec<PendingDylib>,
7417    pending_host: Vec<(
7418        u16,
7419        u16,
7420        Box<[(SmolStr, Box<[TypeExpr]>, TypeExpr, Span)]>,
7421        SmolStr,
7422        Span,
7423    )>,
7424    file_namespaces: &FileNamespaces,
7425    dynamic_libs_fns: &mut Vec<DynamicLibFn>,
7426    host_fns: &mut Vec<HostFnSig>,
7427    dynamic_libs: &mut [Dynamiclib],
7428    sources: &[Source],
7429) {
7430    // An `impl` on a generic type has to name the type arguments it is written
7431    // against: `impl Cell` has no instantiation to attach its methods to.
7432    for func in fns.iter() {
7433        let Some((type_name, _)) = func.name.split_once(METHOD_SEP) else {
7434            continue;
7435        };
7436        if let Some(params) = generics.params_of(type_name) {
7437            error_type_arg_count(
7438                func.name_span,
7439                func.src_file,
7440                type_name,
7441                params.len(),
7442                0,
7443                sources,
7444            );
7445        }
7446    }
7447    for (struct_id, src_file_idx, fields) in pending_structs {
7448        let resolved_fields = fields
7449            .iter()
7450            .map(|(field_name, field_type, field_span)| {
7451                (
7452                    field_name.clone(),
7453                    field_type.to_datatype(&mut TypeCtx {
7454                        file_idx: src_file_idx,
7455                        namespaces: file_namespaces,
7456                        sources,
7457                        structs,
7458                        enums,
7459                        fns,
7460                        generics,
7461                    }),
7462                    *field_span,
7463                )
7464            })
7465            .collect();
7466        structs[struct_id as usize].fields = resolved_fields;
7467    }
7468    for (enum_id, src_file_idx, variants) in pending_enums {
7469        let resolved_variants = variants
7470            .iter()
7471            .map(|(variant_name, payload, name_span)| EnumVariant {
7472                name: variant_name.clone(),
7473                payload: payload
7474                    .iter()
7475                    .map(|t| {
7476                        t.to_datatype(&mut TypeCtx {
7477                            file_idx: src_file_idx,
7478                            namespaces: file_namespaces,
7479                            sources,
7480                            structs,
7481                            enums,
7482                            fns,
7483                            generics,
7484                        })
7485                    })
7486                    .collect(),
7487                name_span: *name_span,
7488            })
7489            .collect();
7490        enums[enum_id as usize].variants = resolved_variants;
7491    }
7492    for (fn_id, src_file_idx, args, return_type, type_params) in pending_fns {
7493        // An annotation naming one of the function's own type parameters is
7494        // left un-pinned: it resolves per call site, once the call names its
7495        // type arguments.
7496        let resolved_args = args
7497            .iter()
7498            .map(|(arg_name, arg_type)| {
7499                (
7500                    arg_name.clone(),
7501                    arg_type
7502                        .clone()
7503                        .filter(|t_e| !t_e.mentions_any(&type_params))
7504                        .map(|t_e| {
7505                            t_e.to_datatype(&mut TypeCtx {
7506                                file_idx: src_file_idx,
7507                                namespaces: file_namespaces,
7508                                sources,
7509                                structs,
7510                                enums,
7511                                fns,
7512                                generics,
7513                            })
7514                        }),
7515                )
7516            })
7517            .collect();
7518        fns[fn_id as usize].args = resolved_args;
7519        fns[fn_id as usize].return_type = return_type
7520            .map(|annotation| *annotation)
7521            .filter(|(t_e, _)| !t_e.mentions_any(&type_params))
7522            .map(|(t_e, t_span)| {
7523                (
7524                    t_e.to_datatype(&mut TypeCtx {
7525                        file_idx: src_file_idx,
7526                        namespaces: file_namespaces,
7527                        sources,
7528                        structs,
7529                        enums,
7530                        fns,
7531                        generics,
7532                    }),
7533                    t_span,
7534                )
7535            });
7536    }
7537    // A struct's own `default` replaces the one its field declarations give,
7538    // so it has to be callable the way that one is.
7539    for func in fns.iter() {
7540        if let Some(struct_id) = struct_of_default_fn(&func.name, structs) {
7541            check_struct_default_fn(
7542                func,
7543                struct_id,
7544                func.return_type.as_ref().map(|(t, span)| (t, *span)),
7545                sources,
7546                TypeNames { structs, enums },
7547            );
7548        }
7549    }
7550    // The signatures a check-only compile bound without a library, which take
7551    // ids past the end of the bound ones so each still names one function.
7552    #[cfg(not(target_arch = "wasm32"))]
7553    let mut unbound_dylib_fns = 0_usize;
7554    #[cfg(target_arch = "wasm32")]
7555    let unbound_dylib_fns = 0_usize;
7556    for (src_file_idx, dynlib_id, fn_signatures, lib, (library_spec, origin), span) in
7557        pending_dylibs
7558    {
7559        // An intrinsic binding holds no library open.
7560        #[cfg(target_arch = "wasm32")]
7561        let () = lib;
7562        let resolved: Vec<FnSignature> = fn_signatures
7563            .iter()
7564            .map(|(fn_name, fn_args, fn_return_type, fn_name_span)| {
7565                let fn_args = fn_args
7566                    .iter()
7567                    .map(|t| {
7568                        t.to_datatype(&mut TypeCtx {
7569                            file_idx: src_file_idx,
7570                            namespaces: file_namespaces,
7571                            sources,
7572                            structs,
7573                            enums,
7574                            fns,
7575                            generics,
7576                        })
7577                    })
7578                    .collect::<Vec<DataType>>()
7579                    .into_boxed_slice();
7580                let fn_return_type = fn_return_type.to_datatype(&mut TypeCtx {
7581                    file_idx: src_file_idx,
7582                    namespaces: file_namespaces,
7583                    sources,
7584                    structs,
7585                    enums,
7586                    fns,
7587                    generics,
7588                });
7589                // Every type a C function takes or returns needs a C shape;
7590                // one that has none is refused here, at the signature, rather
7591                // than when the calling interface is built from it.
7592                if let Some(ty) = fn_args
7593                    .iter()
7594                    .chain(std::iter::once(&fn_return_type))
7595                    .find(|ty| !has_c_representation(ty, structs, 0))
7596                {
7597                    error_type_has_no_c_representation(
7598                        fn_name,
7599                        ty,
7600                        *fn_name_span,
7601                        src_file_idx,
7602                        sources,
7603                        TypeNames { structs, enums },
7604                    );
7605                }
7606                let return_val = FnSignature {
7607                    name: fn_name.clone(),
7608                    args: fn_args.clone(),
7609                    return_type: fn_return_type.clone(),
7610                    id: (dynamic_libs_fns.len() + unbound_dylib_fns) as u16,
7611                    variadic: false,
7612                };
7613                // A check-only compile opened no library. The declared
7614                // signature is all a call site reads, so the binding stops at
7615                // it: no symbol, no calling interface, nothing to run.
7616                #[cfg(not(target_arch = "wasm32"))]
7617                let Some(lib) = lib.as_ref() else {
7618                    unbound_dylib_fns += 1;
7619                    return return_val;
7620                };
7621                #[cfg(not(target_arch = "wasm32"))]
7622                let arg_types: Vec<_> = fn_args.iter().map(|t| t.to_c_type(structs)).collect();
7623                #[cfg(not(target_arch = "wasm32"))]
7624                let return_type = fn_return_type.to_c_type(structs);
7625                #[cfg(not(target_arch = "wasm32"))]
7626                let cif = libffi::middle::Cif::new(arg_types, return_type);
7627                #[cfg(not(target_arch = "wasm32"))]
7628                let ptr = unsafe {
7629                    libffi::middle::CodePtr(
7630                        lib.get::<*const ()>(fn_name.as_bytes())
7631                            .unwrap_or_else(|_| {
7632                                error_cannot_find_dynlib_symbol(
7633                                    fn_name,
7634                                    *fn_name_span,
7635                                    span,
7636                                    src_file_idx,
7637                                    sources,
7638                                );
7639                            })
7640                            .try_as_raw_ptr()
7641                            .unwrap_unchecked(),
7642                    )
7643                };
7644
7645                let mut types = vec![fn_return_type];
7646                types.extend(fn_args);
7647
7648                // Where nothing loads, the only bindings are the standard
7649                // library's, and each is one of the runtime's intrinsics.
7650                #[cfg(target_arch = "wasm32")]
7651                let intrinsic = candela_vm::intrinsics::lookup(&library_spec, fn_name)
7652                    .unwrap_or_else(|| {
7653                        error_cannot_find_dynlib_symbol(
7654                            fn_name,
7655                            *fn_name_span,
7656                            span,
7657                            src_file_idx,
7658                            sources,
7659                        );
7660                    });
7661
7662                dynamic_libs_fns.push(DynamicLibFn {
7663                    types: Box::from(types),
7664                    library: library_spec.clone(),
7665                    origin,
7666                    symbol: fn_name.clone(),
7667                    #[cfg(not(target_arch = "wasm32"))]
7668                    _lib: Rc::clone(lib),
7669                    #[cfg(not(target_arch = "wasm32"))]
7670                    ptr,
7671                    #[cfg(not(target_arch = "wasm32"))]
7672                    cif,
7673                    #[cfg(target_arch = "wasm32")]
7674                    intrinsic,
7675                });
7676                return_val
7677            })
7678            .collect();
7679        dynamic_libs[dynlib_id as usize].fns = resolved.into_boxed_slice();
7680    }
7681
7682    // Resolve `host "..." { ... }` blocks. Unlike dylibs there is no shared
7683    // object to load or FFI CIF to build: each signature is type-checked and
7684    // recorded as a `HostFnSig` whose `id` the VM later uses to dispatch to the
7685    // Rust closure the embedding `Engine` bound to `(namespace, name)`.
7686    for (src_file_idx, dynlib_id, fn_signatures, host_namespace, _span) in pending_host {
7687        let resolved: Vec<FnSignature> = fn_signatures
7688            .iter()
7689            .map(|(fn_name, fn_args, fn_return_type, _fn_name_span)| {
7690                // A lone `...` sentinel argument marks a variadic host fn: it
7691                // takes no fixed argument types, and the call site forwards
7692                // every supplied argument to the registered closure.
7693                let variadic = fn_args.len() == 1
7694                    && matches!(&fn_args[0], TypeExpr::Identifier(s, _) if s.as_str() == "...");
7695                let fn_args = if variadic {
7696                    Box::from([])
7697                } else {
7698                    fn_args
7699                        .iter()
7700                        .map(|t| {
7701                            t.to_datatype(&mut TypeCtx {
7702                                file_idx: src_file_idx,
7703                                namespaces: file_namespaces,
7704                                sources,
7705                                structs,
7706                                enums,
7707                                fns,
7708                                generics,
7709                            })
7710                        })
7711                        .collect::<Vec<DataType>>()
7712                        .into_boxed_slice()
7713                };
7714                let fn_return_type = fn_return_type.to_datatype(&mut TypeCtx {
7715                    file_idx: src_file_idx,
7716                    namespaces: file_namespaces,
7717                    sources,
7718                    structs,
7719                    enums,
7720                    fns,
7721                    generics,
7722                });
7723                let return_val = FnSignature {
7724                    name: fn_name.clone(),
7725                    args: fn_args.clone(),
7726                    return_type: fn_return_type.clone(),
7727                    id: host_fns.len() as u16,
7728                    variadic,
7729                };
7730
7731                let mut types = vec![fn_return_type];
7732                types.extend(fn_args);
7733                host_fns.push(HostFnSig {
7734                    types: types.into_boxed_slice(),
7735                    namespace: host_namespace.clone(),
7736                    name: fn_name.clone(),
7737                    variadic,
7738                });
7739                return_val
7740            })
7741            .collect();
7742        dynamic_libs[dynlib_id as usize].fns = resolved.into_boxed_slice();
7743    }
7744}
7745
7746/// The complete result of compiling a candela program.
7747///
7748/// In addition to the fields the CLI/VM needs to run `main`, this carries the
7749/// compiler-side tables (`functions`, `dyn_libs`, `namespace`, register
7750/// bookkeeping) that the embedding `Program` keeps alive so it can compile
7751/// additional function specializations on demand for `Program::call`, plus the
7752/// resolved `host_fns` signature table used to dispatch `host` calls.
7753pub struct CompileOutput {
7754    pub instructions: Vec<Instr>,
7755    pub registers: Vec<Data>,
7756    pub pools: Pools,
7757    pub instr_src: Vec<InstrSrc>,
7758    pub callsite_registers: Vec<Vec<u16>>,
7759    pub dyn_lib_fns: Vec<DynamicLibFn>,
7760    pub host_fns: Vec<HostFnSig>,
7761    pub allocated_arg_count: usize,
7762    pub allocated_call_depth: usize,
7763    pub sources: Vec<Source>,
7764    pub structs: Vec<Struct>,
7765    pub enums: Vec<EnumType>,
7766    pub functions: Vec<Function>,
7767    pub dyn_libs: Vec<Dynamiclib>,
7768    /// Every file's scope, so a later compile against this program resolves a
7769    /// name the way the file it is written in does.
7770    pub namespaces: FileNamespaces,
7771    pub const_registers: FxHashMap<Data, u16>,
7772    pub free_registers: Vec<u16>,
7773    /// The registers an indirect call passes its arguments in, kept so a later
7774    /// compile against this program uses the same ones.
7775    pub indirect_registers: IndirectRegisters,
7776    /// The generic declarations and the instantiations made from them, kept so
7777    /// a later compile against this program (an embedding host calling in, a
7778    /// REPL line) resolves them the same way.
7779    pub generics: Generics,
7780    /// Whether the program was compiled in the release profile, so a later
7781    /// compile against it (an export entry point) uses the same one.
7782    pub optimize: bool,
7783}
7784
7785impl CompileOutput {
7786    /// Reports that the program has no entry point, unless it has one.
7787    ///
7788    /// Only the entry file's `main` counts: an imported module's own `main` is
7789    /// ignored. Compiling a file without one is not an error, which is what
7790    /// lets `candela check` pass on a library whose entry has nothing to run;
7791    /// running, building or embedding the program calls this first.
7792    pub fn require_main(&self) {
7793        if !self
7794            .functions
7795            .iter()
7796            .any(|func| func.name == "main" && func.src_file == 0)
7797        {
7798            compiler_errors::error_no_main(&self.sources);
7799        }
7800    }
7801}
7802
7803/// Compiles `contents` and everything it imports into one program.
7804///
7805/// `resolver` says where a library import reads from: the standard library, and
7806/// the root of every package the program depends on.
7807#[must_use]
7808pub fn compile(
7809    contents: String,
7810    filename: &str,
7811    debug: bool,
7812    resolver: &ImportResolver,
7813) -> CompileOutput {
7814    compile_profile(contents, filename, debug, resolver, false)
7815}
7816
7817/// [`compile`] in the profile `optimize` names.
7818///
7819/// `false` is the debug profile every run from source uses, `true` the release
7820/// profile `candela build` uses. The two produce programs that behave alike,
7821/// with the same diagnostics, runtime errors and spans; the release one runs
7822/// faster.
7823#[must_use]
7824pub fn compile_profile(
7825    contents: String,
7826    filename: &str,
7827    debug: bool,
7828    resolver: &ImportResolver,
7829    optimize: bool,
7830) -> CompileOutput {
7831    #[cfg(not(target_arch = "wasm32"))]
7832    let now = std::time::Instant::now();
7833
7834    // A previous compilation on this thread may have been aborted mid-inference
7835    // by an error unwind; make sure its bookkeeping doesn't leak into this one.
7836    type_system::reset_inference_state();
7837    std_index::set_lib_dir(resolver.lib_dir().map(Path::to_path_buf));
7838
7839    let main_src = Source {
7840        filename: SmolStr::from(filename),
7841        contents,
7842    };
7843
7844    let parsed = parser::parse(&main_src.contents, &main_src);
7845
7846    #[cfg(not(target_arch = "wasm32"))]
7847    if debug {
7848        println!("PARSING TIME: {:.2?}", now.elapsed());
7849    }
7850
7851    let mut variables: Vec<Variable> = Vec::new();
7852    let mut registers: Vec<Data> = Vec::new();
7853    let mut pools: Pools = Pools::new(
7854        Pool::with_capacity(10),
7855        Pool::with_capacity(2),
7856        StringPool::with_capacity(10),
7857    );
7858    let mut instr_src: Vec<InstrSrc> = Vec::new();
7859    let mut callsite_registers: Vec<Vec<u16>> = Vec::new();
7860    let mut functions: Vec<Function> = Vec::new();
7861    let mut structs: Vec<Struct> = Vec::new();
7862    let mut enums: Vec<EnumType> = Vec::new();
7863    let mut dyn_libs: Vec<Dynamiclib> = Vec::new();
7864    let mut dyn_lib_fns: Vec<DynamicLibFn> = Vec::new();
7865    let mut host_fns: Vec<HostFnSig> = Vec::new();
7866    let mut allocated_arg_count = 0;
7867    let mut allocated_call_depth = 0;
7868    let mut const_registers: FxHashMap<Data, u16> = FxHashMap::default();
7869    let mut free_registers = Vec::new();
7870
7871    let mut sources: Vec<Source> = vec![main_src];
7872    let main_path = loaded_file_key(PathBuf::from(filename));
7873    let mut namespace = Namespace::default();
7874
7875    let mut files: FxHashMap<PathBuf, Namespace> = FxHashMap::default();
7876    let mut file_namespaces = FileNamespaces::default();
7877    let mut pending_structs: Vec<(u16, u16, Box<[(SmolStr, TypeExpr, Span)]>)> = Vec::new();
7878    let mut pending_enums: PendingEnums = Vec::new();
7879    let mut pending_fns: PendingFns = Vec::with_capacity(2);
7880    let mut generics = Generics::default();
7881    let mut pending_dylibs: Vec<PendingDylib> = Vec::new();
7882    let mut pending_host: Vec<(
7883        u16,
7884        u16,
7885        Box<[(SmolStr, Box<[TypeExpr]>, TypeExpr, Span)]>,
7886        SmolStr,
7887        Span,
7888    )> = Vec::new();
7889
7890    generics.add_impls(parsed.impls, 0);
7891    parse_toplevel(
7892        parsed.code,
7893        &main_path,
7894        0,
7895        &mut functions,
7896        &mut structs,
7897        &mut enums,
7898        &mut dyn_libs,
7899        &mut sources,
7900        &mut namespace,
7901        &mut files,
7902        &mut file_namespaces,
7903        &mut pending_structs,
7904        &mut pending_enums,
7905        &mut pending_fns,
7906        &mut pending_dylibs,
7907        &mut pending_host,
7908        &mut generics,
7909        resolver,
7910    );
7911    file_namespaces.insert(0, namespace);
7912    // Before any type is resolved: a name two modules both declare is renamed
7913    // here, and everything downstream reads the name it ended up with.
7914    qualify_duplicate_type_names(
7915        &mut structs,
7916        &mut enums,
7917        &mut functions,
7918        &pending_structs
7919            .iter()
7920            .map(|(struct_id, file_idx, _)| (*struct_id, *file_idx))
7921            .collect::<Vec<(u16, u16)>>(),
7922        &pending_enums
7923            .iter()
7924            .map(|(enum_id, file_idx, _)| (*enum_id, *file_idx))
7925            .collect::<Vec<(u16, u16)>>(),
7926        &file_namespaces,
7927        &sources,
7928    );
7929    resolve_types(
7930        &mut structs,
7931        &mut enums,
7932        &mut functions,
7933        &mut generics,
7934        pending_structs,
7935        pending_enums,
7936        pending_fns,
7937        pending_dylibs,
7938        pending_host,
7939        &file_namespaces,
7940        &mut dyn_lib_fns,
7941        &mut host_fns,
7942        &mut dyn_libs,
7943        &sources,
7944    );
7945
7946    let ctx = Ctx {
7947        block_id: 0,
7948        is_compiling_recursive: false,
7949        file_idx: 0,
7950        single_run: true,
7951        in_function: false,
7952        offset: 0,
7953        return_downcast: None,
7954    };
7955    let mut indirect_registers = IndirectRegisters::default();
7956    let mut state = State {
7957        registers: &mut registers,
7958        fns: &mut functions,
7959        structs: &mut structs,
7960        enums: &mut enums,
7961        pools: &mut pools,
7962        instr_src: &mut instr_src,
7963        callsite_registers: &mut callsite_registers,
7964        dyn_libs: &mut dyn_libs,
7965        allocated_arg_count: &mut allocated_arg_count,
7966        allocated_call_depth: &mut allocated_call_depth,
7967        const_registers: &mut const_registers,
7968        free_registers: &mut free_registers,
7969        sources: &mut sources,
7970        reserved_registers: FxHashSet::default(),
7971        value_callsites: FxHashSet::default(),
7972        optimize,
7973        namespaces: &mut file_namespaces,
7974        generics: &mut generics,
7975        indirect_registers: &mut indirect_registers,
7976        propagations: Vec::new(),
7977        fn_returns: Vec::new(),
7978        captured_binders: Vec::new(),
7979    };
7980    // The entry file's `main` is the program's top level. A file that declares
7981    // none compiles to nothing but the halt: its declarations and signatures are
7982    // still resolved, which is what lets `candela check` pass on a library whose
7983    // entry has nothing to run. Starting a program without one is reported where
7984    // the program is run or packaged, not here.
7985    let main_body = state
7986        .fns
7987        .iter()
7988        .find(|func| func.name == "main" && func.src_file == 0)
7989        .map(|func| func.code.clone());
7990    let mut instructions = if let Some(code) = main_body {
7991        compile_expr(&code, &mut variables, ctx, &mut state)
7992    } else {
7993        Vec::new()
7994    };
7995    instructions.push(Instr::Halt(0));
7996    if optimize {
7997        let mut program = flow::Program {
7998            instructions: &mut instructions,
7999            registers: &mut registers,
8000            objs: &pools.objs,
8001            strings: &mut pools.strings,
8002            const_registers: &mut const_registers,
8003            instr_src: &mut instr_src,
8004            callsite_registers: &mut callsite_registers,
8005            functions: &mut functions,
8006            indirect: &indirect_registers,
8007        };
8008        // A struct kept in registers can free one it held in a field, so the
8009        // replacement runs again until it finds nothing more.
8010        while scalar::replace_scalars(&mut program) {}
8011        fold::fold_constants(&mut program);
8012        copies::forward_copies(&mut program);
8013    }
8014    use_immediates(&mut instructions, &[], &registers, &const_registers);
8015    copies::chain_moves(&mut flow::Program {
8016        instructions: &mut instructions,
8017        registers: &mut registers,
8018        objs: &pools.objs,
8019        strings: &mut pools.strings,
8020        const_registers: &mut const_registers,
8021        instr_src: &mut instr_src,
8022        callsite_registers: &mut callsite_registers,
8023        functions: &mut functions,
8024        indirect: &indirect_registers,
8025    });
8026
8027    #[cfg(debug_assertions)]
8028    if debug {
8029        println!("---- DEBUG ----");
8030        if !pools.objs.is_empty() {
8031            println!("---  ARRAYS  ---");
8032            for (i, data) in pools.objs.iter().enumerate() {
8033                println!(" {i} {data:?}");
8034            }
8035        }
8036        println!("-- REGISTERS --");
8037        for (i, data) in registers.iter().enumerate() {
8038            println!(
8039                " [{i}] {}",
8040                data.format(
8041                    &pools.objs,
8042                    &pools.strings,
8043                    &pools.maps,
8044                    &structs,
8045                    &enums,
8046                    true
8047                )
8048            );
8049        }
8050        if !instructions.is_empty() {
8051            println!("-- INSTRUCTIONS --");
8052            for (i, instr) in instructions.iter().enumerate() {
8053                println!(" {i}: {instr:?}");
8054            }
8055        }
8056        println!("------------------");
8057    }
8058
8059    CompileOutput {
8060        instructions,
8061        registers,
8062        pools,
8063        instr_src,
8064        callsite_registers,
8065        dyn_lib_fns,
8066        host_fns,
8067        allocated_arg_count,
8068        allocated_call_depth,
8069        sources,
8070        structs,
8071        enums,
8072        functions,
8073        dyn_libs,
8074        namespaces: file_namespaces,
8075        const_registers,
8076        free_registers,
8077        indirect_registers,
8078        generics,
8079        optimize,
8080    }
8081}