Skip to main content

konjure_lang/
program.rs

1//! Declaration resolution and module linking. Bodies remain source-preserving for debugging.
2use crate::ast::*;
3use crate::parser::{Token, parse, tokenize};
4use serde::{Deserialize, Serialize};
5use std::collections::{BTreeMap, BTreeSet};
6
7pub(crate) const MAIN: &str = "main";
8pub(crate) const BUILTIN: &str = "builtin";
9
10/// A declaration available to editor navigation and generated reference pages.
11#[derive(Clone, Debug, Serialize, Deserialize)]
12pub struct Symbol {
13    /// Unqualified declaration or method name.
14    pub name: String,
15    /// Module-qualified identity used by editor navigation.
16    pub qualified_name: String,
17    /// Declaration category, such as class, function, field, or system.
18    pub kind: String,
19    /// Definition source range in UTF-8 bytes.
20    pub span: Span,
21    /// Human-readable language declaration signature.
22    pub signature: String,
23    /// Whether other supplied modules may access this declaration.
24    pub exported: bool,
25}
26/// A callable component method, used by host action controls.
27#[derive(Clone, Debug, Serialize, Deserialize)]
28pub struct MethodSymbol {
29    /// Canonical component class key in snapshots.
30    pub class: String,
31    /// Method name for `Machine::invoke_entity`.
32    pub name: String,
33    /// Explicit arguments, excluding implicit `self`.
34    pub parameter_count: usize,
35    /// Method definition source location.
36    pub span: Span,
37}
38/// Source-level semantic metadata for a program whose declarations and bodies are checked.
39#[derive(Clone, Debug, Serialize, Deserialize)]
40pub struct ProgramMetadata {
41    /// Callable component methods, including their explicit arity.
42    pub methods: Vec<MethodSymbol>,
43    /// Class, field, function, trait, and system declarations.
44    pub symbols: Vec<Symbol>,
45    /// Source tokens across all linked modules, including comments.
46    pub tokens: Vec<Token>,
47    /// Modules in dependency initialization order.
48    pub modules: Vec<String>,
49    /// Checked expression references; lexical locals never resolve to global values by spelling.
50    pub references: Vec<ExpressionReference>,
51}
52/// Compiler-resolved source identity for an editor inspector. Inspectors do not evaluate source.
53#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
54#[derive(Clone, Debug, Serialize, Deserialize)]
55pub struct ExpressionReference {
56    /// Exact expression source range.
57    pub span: Span,
58    /// Canonical checked type.
59    pub ty: Type,
60    /// Expression category: global, local, constructor, method, or expression.
61    pub kind: String,
62    /// Canonical declaration or global binding identity, when one is resolved.
63    pub identity: Option<String>,
64    /// Whether identity names a stored global value that can be inspected without evaluation.
65    pub inspectable: bool,
66    /// A typed path to accepted snapshot storage, never an expression to execute.
67    pub target: Option<crate::inspection::InspectionTarget>,
68}
69#[derive(Clone, Debug, Default)]
70pub(crate) struct Module {
71    pub classes: BTreeMap<String, Class>,
72    pub enums: BTreeMap<String, Enum>,
73    pub functions: BTreeMap<String, Function>,
74    pub traits: BTreeMap<String, Trait>,
75    pub imports: BTreeSet<String>,
76    pub exports: BTreeSet<String>,
77    pub statements: Vec<Stmt>,
78}
79/// Linked program. It owns all source text; imports never access the filesystem or network.
80#[derive(Clone, Debug)]
81pub struct Program {
82    pub(crate) modules: BTreeMap<String, Module>,
83    pub(crate) methods: BTreeMap<String, BTreeMap<String, Function>>,
84    pub(crate) implementations: BTreeSet<(String, String)>,
85    pub(crate) systems: Vec<(String, System)>,
86    pub(crate) order: Vec<String>,
87    pub(crate) sources: BTreeMap<String, String>,
88    pub(crate) native_signatures: BTreeMap<String, (Vec<Type>, Type)>,
89    pub(crate) binding_types: BTreeMap<(String, usize, usize), Type>,
90    pub(crate) mutating_methods: BTreeSet<(String, String)>,
91    pub(crate) expression_types: BTreeMap<(String, usize, usize), Type>,
92    pub(crate) method_receivers: BTreeMap<(String, usize, usize), Type>,
93    pub(crate) metadata: ProgramMetadata,
94}
95impl Program {
96    /// Editor symbols, highlight tokens, and linked source modules.
97    pub fn metadata(&self) -> &ProgramMetadata {
98        &self.metadata
99    }
100    /// Original UTF-8 source, keyed by module name (`main` and `builtin` are reserved).
101    pub fn sources(&self) -> &BTreeMap<String, String> {
102        &self.sources
103    }
104    pub(crate) fn key(module: &str, name: &str) -> String {
105        if module == MAIN || module == BUILTIN {
106            name.to_owned()
107        } else {
108            format!("{module}.{name}")
109        }
110    }
111    pub(crate) fn resolve(
112        &self,
113        module: &str,
114        name: &str,
115        span: &Span,
116    ) -> Result<(String, String), Diagnostic> {
117        if let Some((prefix, local)) = name.rsplit_once('.') {
118            let current = &self.modules[module];
119            if prefix != module && prefix != BUILTIN && !current.imports.contains(prefix) {
120                return Err(Diagnostic::new(
121                    "unknown_module",
122                    format!("module `{prefix}` is not imported"),
123                    span.clone(),
124                ));
125            }
126            let target = self.modules.get(prefix).ok_or_else(|| {
127                Diagnostic::new(
128                    "unknown_module",
129                    format!("unknown module `{prefix}`"),
130                    span.clone(),
131                )
132            })?;
133            if prefix != module && prefix != BUILTIN && !target.exports.contains(local) {
134                return Err(Diagnostic::new(
135                    "private_name",
136                    format!("`{local}` is not exported by `{prefix}`"),
137                    span.clone(),
138                ));
139            }
140            return Ok((prefix.to_owned(), local.to_owned()));
141        }
142        let m = &self.modules[module];
143        if m.classes.contains_key(name)
144            || m.enums.contains_key(name)
145            || m.functions.contains_key(name)
146            || m.traits.contains_key(name)
147        {
148            return Ok((module.to_owned(), name.to_owned()));
149        }
150        let b = &self.modules[BUILTIN];
151        if b.classes.contains_key(name)
152            || b.enums.contains_key(name)
153            || b.functions.contains_key(name)
154            || b.traits.contains_key(name)
155        {
156            return Ok((BUILTIN.to_owned(), name.to_owned()));
157        }
158        Ok((module.to_owned(), name.to_owned()))
159    }
160    pub(crate) fn class(
161        &self,
162        module: &str,
163        name: &str,
164        span: &Span,
165    ) -> Result<(String, &Class), Diagnostic> {
166        let (m, n) = self.resolve(module, name, span)?;
167        let class = self.modules[&m].classes.get(&n).ok_or_else(|| {
168            Diagnostic::new(
169                "unknown_class",
170                format!("unknown class `{name}`"),
171                span.clone(),
172            )
173        })?;
174        Ok((m, class))
175    }
176    pub(crate) fn class_by_key(&self, key: &str) -> Option<(&str, &Class)> {
177        self.modules.iter().find_map(|(m, module)| {
178            module
179                .classes
180                .values()
181                .find(|c| Self::key(m, &c.name) == key)
182                .map(|c| (m.as_str(), c))
183        })
184    }
185    pub(crate) fn enum_by_key(&self, key: &str) -> Option<(&str, &Enum)> {
186        self.modules.iter().find_map(|(module, declarations)| {
187            declarations
188                .enums
189                .values()
190                .find(|value| Self::key(module, &value.name) == key)
191                .map(|value| (module.as_str(), value))
192        })
193    }
194    pub(crate) fn variant(
195        &self,
196        module: &str,
197        name: &str,
198        span: &Span,
199    ) -> Result<Option<(Type, String, Vec<Type>)>, Diagnostic> {
200        let Some((owner, case)) = name.rsplit_once('.') else {
201            return Ok(None);
202        };
203        let (target, local) = self.resolve(module, owner, span)?;
204        let Some(declaration) = self.modules[&target].enums.get(&local) else {
205            return Ok(None);
206        };
207        let variant = declaration
208            .cases
209            .iter()
210            .find(|variant| variant.name == case)
211            .ok_or_else(|| {
212                Diagnostic::new(
213                    "unknown_case",
214                    format!("{owner} has no case {case}"),
215                    span.clone(),
216                )
217            })?;
218        let payload = variant
219            .payload
220            .iter()
221            .map(|ty| self.canonical_type(&target, ty, span))
222            .collect::<Result<_, _>>()?;
223        Ok(Some((
224            Type::named(Self::key(&target, &local)),
225            case.into(),
226            payload,
227        )))
228    }
229    pub(crate) fn canonical_type(
230        &self,
231        module: &str,
232        ty: &Type,
233        span: &Span,
234    ) -> Result<Type, Diagnostic> {
235        match ty {
236            Type::Applied { name, arguments } => Ok(Type::Applied {
237                name: name.clone(),
238                arguments: arguments
239                    .iter()
240                    .map(|ty| self.canonical_type(module, ty, span))
241                    .collect::<Result<_, _>>()?,
242            }),
243            Type::Function {
244                parameters,
245                returns,
246            } => Ok(Type::function(
247                parameters
248                    .iter()
249                    .map(|ty| self.canonical_type(module, ty, span))
250                    .collect::<Result<_, _>>()?,
251                self.canonical_type(module, returns, span)?,
252            )),
253            Type::ComponentReference(inner) => Ok(Type::ComponentReference(Box::new(
254                self.canonical_type(module, inner, span)?,
255            ))),
256            Type::Tensor(inner) => {
257                let inner = self.canonical_type(module, inner, span)?;
258                if crate::data::dtype(&inner).is_none() {
259                    return Err(Diagnostic::new(
260                        "invalid_dtype",
261                        "Tensor[T] requires a numeric dtype",
262                        span.clone(),
263                    ));
264                }
265                Ok(Type::Tensor(Box::new(inner)))
266            }
267            Type::List(inner) => Ok(Type::List(Box::new(
268                self.canonical_type(module, inner, span)?,
269            ))),
270            Type::Named(name)
271                if crate::data::is_primitive(name) || matches!(name.as_str(), "Any" | "List") =>
272            {
273                Ok(Type::named(name))
274            }
275            Type::Named(name) => {
276                // Checked signatures can carry private nominal types through an
277                // exported function. Their canonical identity is already resolved.
278                if name.contains('.')
279                    && (self.class_by_key(name).is_some()
280                        || self.enum_by_key(name).is_some()
281                        || self.modules.iter().any(|(m, declarations)| {
282                            declarations
283                                .traits
284                                .contains_key(name.strip_prefix(&format!("{m}.")).unwrap_or(""))
285                        }))
286                {
287                    return Ok(ty.clone());
288                }
289                let (m, n) = self.resolve(module, name, span)?;
290                Ok(Type::named(Self::key(&m, &n)))
291            }
292        }
293    }
294    pub(crate) fn typed_builtin_signature(
295        &self,
296        name: &str,
297        ty: &Type,
298        span: &Span,
299    ) -> Result<Type, Diagnostic> {
300        if name == "tensor" {
301            if crate::data::dtype(ty).is_none() {
302                return Err(Diagnostic::new(
303                    "invalid_dtype",
304                    "tensor[T] requires a numeric dtype",
305                    span.clone(),
306                ));
307            }
308            return Ok(Type::function(
309                vec![Type::List(Box::new(ty.clone())), crate::data::shape_type()],
310                crate::data::result_type(crate::data::tensor_type(ty.clone())),
311            ));
312        }
313        let Type::Named(key) = ty else {
314            return Err(Diagnostic::new(
315                "unknown_query_type",
316                "ECS type argument must be a class or trait",
317                span.clone(),
318            ));
319        };
320        let concrete = self.class_by_key(key).is_some();
321        let contract = self.modules.iter().any(|(module, declarations)| {
322            declarations
323                .traits
324                .keys()
325                .any(|local| Self::key(module, local) == *key)
326        });
327        if !matches!(name, "get" | "has" | "query" | "bind") {
328            return Err(Diagnostic::new(
329                "invalid_type_argument",
330                "only get, has, query, and bind accept a type argument",
331                span.clone(),
332            ));
333        }
334        if !concrete && (name != "query" || !contract) {
335            return Err(Diagnostic::new(
336                "unknown_query_type",
337                format!(
338                    "{name}<T> requires {}",
339                    if name == "query" {
340                        "a class or trait"
341                    } else {
342                        "a concrete class"
343                    }
344                ),
345                span.clone(),
346            ));
347        }
348        let parameters = if name == "query" {
349            vec![]
350        } else {
351            vec![Type::named("Entity")]
352        };
353        let returns = match name {
354            "get" => ty.clone(),
355            "has" => Type::named("Bool"),
356            "bind" => Type::ComponentReference(Box::new(ty.clone())),
357            _ => Type::List(Box::new(Type::named("Entity"))),
358        };
359        Ok(Type::function(parameters, returns))
360    }
361    fn same_signature(&self, actual: &Function, required: &Function) -> bool {
362        actual.parameters.len() == required.parameters.len()
363            && actual
364                .parameters
365                .iter()
366                .zip(&required.parameters)
367                .all(|(a, b)| {
368                    self.canonical_type(&actual.span.module, &a.ty, &a.span)
369                        .ok()
370                        == self
371                            .canonical_type(&required.span.module, &b.ty, &b.span)
372                            .ok()
373                })
374            && self
375                .canonical_type(&actual.span.module, &actual.returns, &actual.span)
376                .ok()
377                == self
378                    .canonical_type(&required.span.module, &required.returns, &required.span)
379                    .ok()
380    }
381    pub(crate) fn query_classes(
382        &self,
383        module: &str,
384        name: &str,
385        span: &Span,
386    ) -> Result<Vec<String>, Diagnostic> {
387        let (m, n) = self.resolve(module, name, span)?;
388        let key = Self::key(&m, &n);
389        if self.modules[&m].classes.contains_key(&n) {
390            return Ok(vec![key]);
391        }
392        if self.modules[&m].traits.contains_key(&n) {
393            return Ok(self
394                .implementations
395                .iter()
396                .filter_map(|(class, t)| (t == &key).then_some(class.clone()))
397                .collect());
398        }
399        Err(Diagnostic::new(
400            "unknown_query_type",
401            format!("`{name}` is not a class or trait"),
402            span.clone(),
403        ))
404    }
405    pub(crate) fn has_type(&self, module: &str, ty: &Type, span: &Span) -> Result<(), Diagnostic> {
406        match ty {
407            Type::Applied { name, arguments } => {
408                let arity = match name.as_str() {
409                    "Res" => 2,
410                    "Opt" => 1,
411                    _ => {
412                        return Err(Diagnostic::new(
413                            "unknown_type",
414                            format!("unknown generic type {name}"),
415                            span.clone(),
416                        ));
417                    }
418                };
419                if arguments.len() != arity {
420                    return Err(Diagnostic::new(
421                        "type_arity",
422                        format!("{name} requires {arity} type arguments"),
423                        span.clone(),
424                    ));
425                }
426                for argument in arguments {
427                    self.has_type(module, argument, span)?;
428                }
429                Ok(())
430            }
431            Type::Tensor(inner) => {
432                if crate::data::dtype(inner).is_none() {
433                    return Err(Diagnostic::new(
434                        "invalid_dtype",
435                        "Tensor[T] requires a numeric dtype",
436                        span.clone(),
437                    ));
438                }
439                Ok(())
440            }
441            Type::List(inner) => self.has_type(module, inner, span),
442            Type::ComponentReference(inner) => {
443                let Type::Named(name) = inner.as_ref() else {
444                    return Err(Diagnostic::new(
445                        "unknown_type",
446                        "ComponentReference[T] requires a concrete class",
447                        span.clone(),
448                    ));
449                };
450                self.class(module, name, span).map(|_| ())
451            }
452            Type::Function {
453                parameters,
454                returns,
455            } => {
456                for ty in parameters.iter().chain(std::iter::once(returns.as_ref())) {
457                    self.has_type(module, ty, span)?;
458                }
459                Ok(())
460            }
461            Type::Named(name)
462                if crate::data::is_primitive(name) || matches!(name.as_str(), "Any" | "List") =>
463            {
464                Ok(())
465            }
466            Type::Named(name) => {
467                let (m, n) = self.resolve(module, name, span)?;
468                if self.modules[&m].classes.contains_key(&n)
469                    || self.modules[&m].enums.contains_key(&n)
470                    || self.modules[&m].traits.contains_key(&n)
471                {
472                    Ok(())
473                } else {
474                    Err(Diagnostic::new(
475                        "unknown_type",
476                        format!("unknown type `{name}`"),
477                        span.clone(),
478                    ))
479                }
480            }
481        }
482    }
483}
484pub(crate) fn type_text(ty: &Type) -> String {
485    match ty {
486        Type::Applied { name, arguments } => format!(
487            "{name}[{}]",
488            arguments
489                .iter()
490                .map(type_text)
491                .collect::<Vec<_>>()
492                .join(", ")
493        ),
494        Type::Named(n) => n.clone(),
495        Type::Tensor(t) => format!("Tensor[{}]", type_text(t)),
496        Type::List(t) => format!("List[{}]", type_text(t)),
497        Type::ComponentReference(t) => format!("ComponentReference[{}]", type_text(t)),
498        Type::Function {
499            parameters,
500            returns,
501        } => format!(
502            "func({}) -> {}",
503            parameters
504                .iter()
505                .map(type_text)
506                .collect::<Vec<_>>()
507                .join(", "),
508            type_text(returns)
509        ),
510    }
511}
512pub(crate) fn function_text(f: &Function) -> String {
513    format!(
514        "func {}({}) -> {}",
515        f.name,
516        f.parameters
517            .iter()
518            .map(|p| format!("{}: {}", p.name, type_text(&p.ty)))
519            .collect::<Vec<_>>()
520            .join(", "),
521        type_text(&f.returns)
522    )
523}
524fn symbol(
525    module: &str,
526    name: &str,
527    kind: &str,
528    span: &Span,
529    signature: String,
530    exports: &BTreeSet<String>,
531) -> Symbol {
532    Symbol {
533        name: name.into(),
534        qualified_name: format!("{module}.{name}"),
535        kind: kind.into(),
536        span: span.clone(),
537        signature,
538        exported: module == MAIN || module == BUILTIN || exports.contains(name),
539    }
540}
541
542fn pure_default(expr: &Expr) -> bool {
543    match &expr.kind {
544        ExprKind::Assign { .. } => false,
545        ExprKind::TensorLiteral { values, .. } => values.iter().all(pure_default),
546        ExprKind::Match { value, arms } => {
547            pure_default(value) && arms.iter().all(|arm| pure_default(&arm.value))
548        }
549        ExprKind::Propagate(value) => pure_default(value),
550        ExprKind::TypedMethod { object, .. } => pure_default(object),
551        ExprKind::TypedLiteral { value, .. } => pure_default(value),
552        ExprKind::Number(_) | ExprKind::Bool(_) | ExprKind::Text(_) | ExprKind::Unit => true,
553        ExprKind::Name(_) => true,
554        ExprKind::List(values) => values.iter().all(pure_default),
555        ExprKind::Record { fields, .. } => fields.values().all(pure_default),
556        ExprKind::Unary { value, .. } => pure_default(value),
557        ExprKind::Binary { left, right, .. } => pure_default(left) && pure_default(right),
558        ExprKind::Field { object, .. } => pure_default(object),
559        ExprKind::Index { object, indices } => {
560            pure_default(object)
561                && indices.iter().all(|index| match index {
562                    IndexExpr::Index(e) => pure_default(e),
563                    IndexExpr::Slice { start, stop, step } => [start, stop, step]
564                        .into_iter()
565                        .flatten()
566                        .all(|expr| pure_default(expr)),
567                    IndexExpr::NewAxis | IndexExpr::Ellipsis => true,
568                })
569        }
570        ExprKind::TypedFunction { .. } => true,
571        ExprKind::TypedCall {
572            name, arguments, ..
573        } => {
574            matches!(
575                name.as_str(),
576                "tensor" | "convert" | "builtin.tensor" | "builtin.convert"
577            ) && arguments.iter().all(pure_default)
578        }
579        ExprKind::Call { callee, arguments } => {
580            let name = match &callee.kind {
581                ExprKind::Name(n) => Some(n.as_str()),
582                ExprKind::Field { object, field } if matches!(&object.kind,ExprKind::Name(n) if n=="builtin") => {
583                    Some(field.as_str())
584                }
585                _ => None,
586            };
587            name.is_some_and(|n| {
588                matches!(
589                    n,
590                    "sin"
591                        | "cos"
592                        | "sqrt"
593                        | "abs"
594                        | "floor"
595                        | "ceil"
596                        | "min"
597                        | "max"
598                        | "clamp"
599                        | "pow"
600                        | "len"
601                        | "append"
602                        | "range"
603                )
604            }) && arguments.iter().all(pure_default)
605        }
606    }
607}
608
609/// Parse declarations, resolve explicit module imports, and validate type/trait contracts.
610///
611/// All authored declarations and bodies are statically checked before initialization.
612/// Runtime value and resource checks preserve transactional execution.
613///
614/// # Errors
615/// Returns source-linked syntax, declaration, module, or trait-contract diagnostics.
616pub fn compile(
617    source: &str,
618    supplied: &BTreeMap<String, String>,
619) -> Result<Program, Vec<Diagnostic>> {
620    compile_with_registry(source, supplied, &crate::NativeRegistry::default())
621}
622
623/// Compile with explicit pure host signatures available to the static checker.
624/// The machine must receive a registry with these same signatures.
625/// # Errors
626/// Returns source-linked syntax, declaration, type, or trait diagnostics.
627pub fn compile_with_registry(
628    source: &str,
629    supplied: &BTreeMap<String, String>,
630    registry: &crate::NativeRegistry,
631) -> Result<Program, Vec<Diagnostic>> {
632    let mut sources = BTreeMap::from([
633        (MAIN.to_owned(), source.to_owned()),
634        (BUILTIN.to_owned(), crate::catalog::PRELUDE.to_owned()),
635    ]);
636    let mut parsed = BTreeMap::<String, Vec<Item>>::new();
637    let mut order = Vec::new();
638    let mut visiting = BTreeSet::new();
639    let mut errors = Vec::new();
640    fn load(
641        name: &str,
642        supplied: &BTreeMap<String, String>,
643        sources: &mut BTreeMap<String, String>,
644        parsed: &mut BTreeMap<String, Vec<Item>>,
645        order: &mut Vec<String>,
646        visiting: &mut BTreeSet<String>,
647        errors: &mut Vec<Diagnostic>,
648    ) {
649        if parsed.contains_key(name) {
650            return;
651        }
652        if order.len() + visiting.len() >= 64 {
653            errors.push(Diagnostic::new(
654                "module_limit",
655                "program exceeds 64 modules",
656                Span::new(name, 0, 0),
657            ));
658            return;
659        }
660        if !visiting.insert(name.to_owned()) {
661            errors.push(Diagnostic::new(
662                "cyclic_import",
663                format!("cyclic import of `{name}`"),
664                Span::new(name, 0, 0),
665            ));
666            return;
667        }
668        let Some(source) = sources
669            .get(name)
670            .cloned()
671            .or_else(|| supplied.get(name).cloned())
672        else {
673            errors.push(Diagnostic::new(
674                "missing_module",
675                format!("module `{name}` was not supplied"),
676                Span::new(name, 0, 0),
677            ));
678            visiting.remove(name);
679            return;
680        };
681        sources.insert(name.to_owned(), source.clone());
682        match parse(&source, name) {
683            Ok(items) => {
684                for item in &items {
685                    if let Item::Import { module, span } = item {
686                        if module == MAIN {
687                            errors.push(Diagnostic::new(
688                                "reserved_module",
689                                "cannot import reserved entry module `main`",
690                                span.clone(),
691                            ));
692                            continue;
693                        }
694                        load(module, supplied, sources, parsed, order, visiting, errors);
695                    }
696                }
697                parsed.insert(name.to_owned(), items);
698                order.push(name.to_owned());
699            }
700            Err(mut diagnostics) => errors.append(&mut diagnostics),
701        }
702        visiting.remove(name);
703    }
704    load(
705        BUILTIN,
706        supplied,
707        &mut sources,
708        &mut parsed,
709        &mut order,
710        &mut visiting,
711        &mut errors,
712    );
713    load(
714        MAIN,
715        supplied,
716        &mut sources,
717        &mut parsed,
718        &mut order,
719        &mut visiting,
720        &mut errors,
721    );
722    if !errors.is_empty() {
723        return Err(errors);
724    }
725    let mut program = Program {
726        modules: BTreeMap::new(),
727        methods: BTreeMap::new(),
728        implementations: BTreeSet::new(),
729        systems: Vec::new(),
730        order,
731        sources,
732        native_signatures: registry.signatures(),
733        binding_types: BTreeMap::new(),
734        mutating_methods: BTreeSet::new(),
735        expression_types: BTreeMap::new(),
736        method_receivers: BTreeMap::new(),
737        metadata: ProgramMetadata {
738            methods: Vec::new(),
739            symbols: Vec::new(),
740            tokens: Vec::new(),
741            modules: Vec::new(),
742            references: Vec::new(),
743        },
744    };
745    let mut implementations = Vec::new();
746    for module_name in &program.order {
747        let mut module = Module::default();
748        if module_name == BUILTIN {
749            module.enums.insert(
750                "DataError".into(),
751                Enum {
752                    name: "DataError".into(),
753                    cases: konjure_sdk::data::DataError::VARIANTS
754                        .iter()
755                        .map(|case| EnumCase {
756                            name: case.name.into(),
757                            payload: vec![],
758                            span: Span::new(BUILTIN, 0, 0),
759                        })
760                        .collect(),
761                    span: Span::new(BUILTIN, 0, 0),
762                },
763            );
764        }
765        let mut names = BTreeSet::new();
766        for item in &parsed[module_name] {
767            let named = match item {
768                Item::Class(c) => Some((&c.name, &c.span)),
769                Item::Enum(e) => Some((&e.name, &e.span)),
770                Item::Function(f) => Some((&f.name, &f.span)),
771                Item::Trait(t) => Some((&t.name, &t.span)),
772                Item::System(s) => Some((&s.name, &s.span)),
773                _ => None,
774            };
775            if let Some((name, span)) = named
776                && !names.insert(name.clone())
777            {
778                errors.push(Diagnostic::new(
779                    "duplicate_name",
780                    format!("duplicate declaration `{name}`"),
781                    span.clone(),
782                ));
783            }
784            match item {
785                Item::Enum(e) => {
786                    let mut cases = BTreeSet::new();
787                    for case in &e.cases {
788                        if !cases.insert(&case.name) {
789                            errors.push(Diagnostic::new(
790                                "duplicate_case",
791                                format!("duplicate enum case {}", case.name),
792                                case.span.clone(),
793                            ));
794                        }
795                    }
796                    module.enums.insert(e.name.clone(), e.clone());
797                }
798                Item::Class(c) => {
799                    let mut fields = BTreeSet::new();
800                    for f in &c.fields {
801                        if !fields.insert(&f.name) {
802                            errors.push(Diagnostic::new(
803                                "duplicate_field",
804                                format!("duplicate field `{}`", f.name),
805                                f.span.clone(),
806                            ));
807                        }
808                    }
809                    implementations.push((
810                        module_name.clone(),
811                        Implementation {
812                            trait_name: None,
813                            class: c.name.clone(),
814                            methods: c.methods.clone(),
815                            span: c.span.clone(),
816                        },
817                    ));
818                    module.classes.insert(c.name.clone(), c.clone());
819                }
820                Item::Function(f) => {
821                    module.functions.insert(f.name.clone(), f.clone());
822                }
823                Item::Trait(t) => {
824                    module.traits.insert(t.name.clone(), t.clone());
825                }
826                Item::System(s) => program.systems.push((module_name.clone(), s.clone())),
827                Item::Implementation(i) => implementations.push((module_name.clone(), i.clone())),
828                Item::Import { module: m, .. } => {
829                    module.imports.insert(m.clone());
830                }
831                Item::Export { name, .. } => {
832                    module.exports.insert(name.clone());
833                }
834                Item::Statement(s) => module.statements.push(s.clone()),
835            }
836        }
837        for item in &parsed[module_name] {
838            if let Item::Export { name, span } = item
839                && !names.contains(name)
840                && !module
841                    .statements
842                    .iter()
843                    .any(|s| matches!(&s.kind,StmtKind::Let{name:n,..} if n==name))
844            {
845                errors.push(Diagnostic::new(
846                    "unknown_export",
847                    format!("cannot export undefined name `{name}`"),
848                    span.clone(),
849                ));
850            }
851        }
852        program.modules.insert(module_name.clone(), module);
853    }
854    for (m, module) in &program.modules {
855        if m != BUILTIN {
856            for item in &parsed[m] {
857                let named = match item {
858                    Item::Class(c) => Some((&c.name, &c.span)),
859                    Item::Enum(e) => Some((&e.name, &e.span)),
860                    Item::Function(f) => Some((&f.name, &f.span)),
861                    Item::Trait(t) => Some((&t.name, &t.span)),
862                    _ => None,
863                };
864                if let Some((name, span)) = named {
865                    let builtin = &program.modules[BUILTIN];
866                    if builtin.classes.contains_key(name)
867                        || builtin.enums.contains_key(name)
868                        || builtin.traits.contains_key(name)
869                        || crate::native::is_builtin(name)
870                        || crate::data::is_primitive(name)
871                        || matches!(
872                            name.as_str(),
873                            "Any"
874                                | "List"
875                                | "Tensor"
876                                | "ComponentReference"
877                                | "Res"
878                                | "Opt"
879                                | "Never"
880                                | "Ok"
881                                | "Err"
882                                | "Some"
883                                | "None"
884                        )
885                    {
886                        errors.push(Diagnostic::new(
887                            "reserved_name",
888                            format!("`{name}` is provided by the builtin namespace"),
889                            span.clone(),
890                        ));
891                    }
892                }
893            }
894        }
895        let validate_function = |f: &Function, errors: &mut Vec<Diagnostic>| {
896            let mut parameters = BTreeSet::new();
897            for p in &f.parameters {
898                if !parameters.insert(&p.name) {
899                    errors.push(Diagnostic::new(
900                        "duplicate_parameter",
901                        format!("duplicate parameter `{}`", p.name),
902                        p.span.clone(),
903                    ));
904                }
905                if let Err(e) = program.has_type(m, &p.ty, &p.span) {
906                    errors.push(e);
907                }
908            }
909            if let Err(e) = program.has_type(m, &f.returns, &f.span) {
910                errors.push(e);
911            }
912        };
913        for f in module.functions.values() {
914            validate_function(f, &mut errors);
915        }
916        for declaration in module.enums.values() {
917            for case in &declaration.cases {
918                for ty in &case.payload {
919                    if let Err(error) = program.has_type(m, ty, &case.span) {
920                        errors.push(error);
921                    }
922                }
923            }
924        }
925        for t in module.traits.values() {
926            let mut methods = BTreeSet::new();
927            for f in &t.methods {
928                validate_function(f, &mut errors);
929                if !methods.insert(&f.name) {
930                    errors.push(Diagnostic::new(
931                        "duplicate_method",
932                        format!("duplicate trait method `{}`", f.name),
933                        f.span.clone(),
934                    ));
935                }
936            }
937        }
938        for c in module.classes.values() {
939            for field in &c.fields {
940                if let Some(default) = &field.default
941                    && !pure_default(default)
942                {
943                    errors.push(Diagnostic::new("impure_default","field defaults allow only constant values, constructors, and pure builtin expressions",default.span.clone()));
944                }
945                if let Err(e) = program.has_type(m, &field.ty, &field.span) {
946                    errors.push(e);
947                }
948            }
949        }
950    }
951    for (module, i) in implementations {
952        let (class_module, class) = match program.class(&module, &i.class, &i.span) {
953            Ok(c) => c,
954            Err(e) => {
955                errors.push(e);
956                continue;
957            }
958        };
959        let class_key = Program::key(&class_module, &class.name);
960        if let Some(trait_name) = &i.trait_name {
961            match program.resolve(&module, trait_name, &i.span) {
962                Ok((tm, tn)) => {
963                    if let Some(t) = program.modules[&tm].traits.get(&tn) {
964                        for required in &t.methods {
965                            match i.methods.iter().find(|f| f.name == required.name) {
966                                Some(actual) if program.same_signature(actual, required) => {}
967                                _ => errors.push(Diagnostic::new(
968                                    "trait_contract",
969                                    format!(
970                                        "implementation of `{trait_name}` needs `{}`",
971                                        function_text(required)
972                                    ),
973                                    i.span.clone(),
974                                )),
975                            }
976                        }
977                        for method in &i.methods {
978                            if !t.methods.iter().any(|r| r.name == method.name) {
979                                errors.push(Diagnostic::new(
980                                    "trait_contract",
981                                    format!(
982                                        "`{}` is not a member of trait `{trait_name}`",
983                                        method.name
984                                    ),
985                                    method.span.clone(),
986                                ));
987                            }
988                        }
989                        program
990                            .implementations
991                            .insert((class_key.clone(), Program::key(&tm, &tn)));
992                    } else {
993                        errors.push(Diagnostic::new(
994                            "unknown_trait",
995                            format!("unknown trait `{trait_name}`"),
996                            i.span.clone(),
997                        ));
998                    }
999                }
1000                Err(e) => errors.push(e),
1001            }
1002        }
1003        for method in &i.methods {
1004            let mut parameters = std::collections::BTreeSet::new();
1005            for parameter in &method.parameters {
1006                if parameter.name == "self" || !parameters.insert(&parameter.name) {
1007                    errors.push(Diagnostic::new(
1008                        "duplicate_parameter",
1009                        "method parameters must be unique and cannot redeclare implicit self",
1010                        parameter.span.clone(),
1011                    ));
1012                }
1013            }
1014            if let Err(e) = program.has_type(&module, &method.returns, &method.span) {
1015                errors.push(e);
1016            }
1017            for param in &method.parameters {
1018                if let Err(e) = program.has_type(&module, &param.ty, &param.span) {
1019                    errors.push(e);
1020                }
1021            }
1022        }
1023        let methods = program.methods.entry(class_key).or_default();
1024        for method in i.methods {
1025            if methods
1026                .insert(method.name.clone(), method.clone())
1027                .is_some()
1028            {
1029                errors.push(Diagnostic::new(
1030                    "duplicate_method",
1031                    format!("duplicate method `{}`", method.name),
1032                    method.span,
1033                ));
1034            }
1035        }
1036    }
1037    for (m, system) in &program.systems {
1038        if system.bindings.is_empty() {
1039            errors.push(Diagnostic::new(
1040                "system_query",
1041                "system needs at least one component binding",
1042                system.span.clone(),
1043            ));
1044        }
1045        let mut bindings = BTreeSet::new();
1046        let mut classes = BTreeSet::new();
1047        for binding in &system.bindings {
1048            if !bindings.insert(binding.name.clone())
1049                || matches!(binding.name.as_str(), "entity" | "time" | "tick" | "dt")
1050            {
1051                errors.push(Diagnostic::new(
1052                    "system_binding",
1053                    "component bindings must be unique and cannot use entity, time, tick, or dt",
1054                    binding.span.clone(),
1055                ));
1056            }
1057            let Type::Named(name) = &binding.ty else {
1058                errors.push(Diagnostic::new(
1059                    "system_query",
1060                    "component selector must name a class or trait",
1061                    binding.span.clone(),
1062                ));
1063                continue;
1064            };
1065            if let Err(e) = program.query_classes(m, name, &binding.span) {
1066                errors.push(e);
1067            }
1068            if system.bindings.len() > 1 {
1069                match program.class(m, name, &binding.span) {
1070                    Ok((module, class)) => {
1071                        if !classes.insert(Program::key(&module, &class.name)) {
1072                            errors.push(Diagnostic::new(
1073                                "system_alias",
1074                                "a join cannot bind the same component class twice",
1075                                binding.span.clone(),
1076                            ));
1077                        }
1078                    }
1079                    Err(e) => errors.push(e),
1080                }
1081            }
1082        }
1083        let mut callbacks = BTreeSet::new();
1084        for callback in &system.callbacks {
1085            if !callbacks.insert(callback.name.clone()) {
1086                errors.push(Diagnostic::new(
1087                    "duplicate_callback",
1088                    format!("duplicate system callback `{}`", callback.name),
1089                    callback.span.clone(),
1090                ));
1091            }
1092            let valid = (callback.returns == Type::named("Unit")
1093                || callback.returns == crate::data::result_type(Type::named("Unit")))
1094                && match callback.name.as_str() {
1095                    "init" | "done" => callback.parameters.is_empty(),
1096                    "frame" => {
1097                        callback.parameters.len() == 1
1098                            && callback.parameters[0].ty == Type::named("f64")
1099                    }
1100                    _ => false,
1101                };
1102            if !valid {
1103                errors.push(Diagnostic::new("lifecycle_signature", "system callbacks are init() -> Unit, frame(dt: Number) -> Unit, and done() -> Unit", callback.span.clone()));
1104            }
1105            for parameter in &callback.parameters {
1106                if bindings.contains(&parameter.name)
1107                    || matches!(parameter.name.as_str(), "self" | "entity" | "time" | "tick")
1108                {
1109                    errors.push(Diagnostic::new(
1110                        "system_binding",
1111                        "callback parameter conflicts with a system binding",
1112                        parameter.span.clone(),
1113                    ));
1114                }
1115            }
1116        }
1117        if !callbacks.contains("frame") {
1118            errors.push(Diagnostic::new(
1119                "missing_frame",
1120                "system requires func frame(dt: Number) -> Unit",
1121                system.span.clone(),
1122            ));
1123        }
1124    }
1125    if !errors.is_empty() {
1126        return Err(errors);
1127    }
1128    for (m, module) in &program.modules {
1129        for c in module.classes.values() {
1130            program.metadata.symbols.push(symbol(
1131                m,
1132                &c.name,
1133                "type",
1134                &c.span,
1135                format!("type {}", c.name),
1136                &module.exports,
1137            ));
1138            for field in &c.fields {
1139                program.metadata.symbols.push(symbol(
1140                    m,
1141                    &format!("{}.{}", c.name, field.name),
1142                    "field",
1143                    &field.span,
1144                    format!("{}: {}", field.name, type_text(&field.ty)),
1145                    &module.exports,
1146                ));
1147            }
1148        }
1149        for declaration in module.enums.values() {
1150            program.metadata.symbols.push(symbol(
1151                m,
1152                &declaration.name,
1153                "enum",
1154                &declaration.span,
1155                format!("enum {}", declaration.name),
1156                &module.exports,
1157            ));
1158            for case in &declaration.cases {
1159                program.metadata.symbols.push(symbol(
1160                    m,
1161                    &format!("{}.{}", declaration.name, case.name),
1162                    "case",
1163                    &case.span,
1164                    format!(
1165                        "{}({})",
1166                        case.name,
1167                        case.payload
1168                            .iter()
1169                            .map(type_text)
1170                            .collect::<Vec<_>>()
1171                            .join(", ")
1172                    ),
1173                    &module.exports,
1174                ));
1175            }
1176        }
1177        for f in module.functions.values() {
1178            program.metadata.symbols.push(symbol(
1179                m,
1180                &f.name,
1181                "function",
1182                &f.span,
1183                function_text(f),
1184                &module.exports,
1185            ));
1186        }
1187        for t in module.traits.values() {
1188            program.metadata.symbols.push(symbol(
1189                m,
1190                &t.name,
1191                "trait",
1192                &t.span,
1193                format!("trait {}", t.name),
1194                &module.exports,
1195            ));
1196        }
1197    }
1198    for (m, s) in &program.systems {
1199        program.metadata.symbols.push(symbol(
1200            m,
1201            &s.name,
1202            "system",
1203            &s.span,
1204            format!(
1205                "system {} of ({})",
1206                s.name,
1207                s.bindings
1208                    .iter()
1209                    .map(|b| format!("{}: {}", b.name, type_text(&b.ty)))
1210                    .collect::<Vec<_>>()
1211                    .join(", ")
1212            ),
1213            &program.modules[m].exports,
1214        ));
1215    }
1216    for (m, source) in &program.sources {
1217        if let Ok(mut tokens) = tokenize(source, m) {
1218            program.metadata.tokens.append(&mut tokens);
1219        }
1220    }
1221    for (class, methods) in &program.methods {
1222        for method in methods.values() {
1223            program.metadata.methods.push(MethodSymbol {
1224                class: class.clone(),
1225                name: method.name.clone(),
1226                parameter_count: method.parameters.len(),
1227                span: method.span.clone(),
1228            });
1229        }
1230    }
1231    program.metadata.modules = program.order.clone();
1232    crate::typecheck::check(&mut program, registry)?;
1233    Ok(program)
1234}