From d15a34f9d02831702e80889c498d469cc07c81f1 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Tue, 13 Oct 2026 16:41:47 +0200 Subject: [PATCH] =?UTF-8?q?feat(analyser):=20record=20each=20native's=20re?= =?UTF-8?q?gistry=20slot=20=F0=9F=94=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The resolver records each native symbol's position in the function registry, which is the global slot the VM gives it. The layout pass will turn these into global storage instead of relying on natives being declared first. Co-Authored-By: Claude Opus 5.5 --- ndc_analyser/src/name_resolution.rs | 15 ++++++++++++++- .../src/name_resolution/resolver_tests.rs | 18 ++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/ndc_analyser/src/name_resolution.rs b/ndc_analyser/src/name_resolution.rs index 16421e1f..262f405f 100644 --- a/ndc_analyser/src/name_resolution.rs +++ b/ndc_analyser/src/name_resolution.rs @@ -59,6 +59,16 @@ pub(crate) struct NameResolver { type_names: AHashMap, /// What each struct declaration binds, keyed by the declaration's node. structs: AHashMap, + /// Each native's position in the function registry, which is also its + /// global slot in the VM. With `new(["len", "+"])`, `len` is 0 and `+` is 1. + #[cfg_attr( + not(test), + expect( + dead_code, + reason = "read by the layout pass, which is not written yet" + ) + )] + native_slots: AHashMap, /// Errors from statements that failed to resolve. Resolution carries on with /// the next statement, so `a; b;` reports both unknown names. errors: Vec, @@ -298,9 +308,11 @@ impl NameResolver { let mut scopes = ScopeTree::new(); let mut symbols = SymbolTable::default(); - for builtin in builtins { + let mut native_slots = AHashMap::default(); + for (slot, builtin) in builtins.into_iter().enumerate() { let symbol = symbols.declare(builtin.to_string(), BindingKind::Function); scopes.bind(symbol); + native_slots.insert(symbol, slot); } for name in StaticType::BUILTIN_TYPE_NAMES { let symbol = symbols.declare((*name).to_string(), BindingKind::Type); @@ -321,6 +333,7 @@ impl NameResolver { setters: Default::default(), type_names: Default::default(), structs: Default::default(), + native_slots, errors: Default::default(), } } diff --git a/ndc_analyser/src/name_resolution/resolver_tests.rs b/ndc_analyser/src/name_resolution/resolver_tests.rs index 627af79a..e361fb54 100644 --- a/ndc_analyser/src/name_resolution/resolver_tests.rs +++ b/ndc_analyser/src/name_resolution/resolver_tests.rs @@ -1209,3 +1209,21 @@ fn statements_after_an_error_still_resolve() { assert_eq!(uses(&resolver, x), 1); assert!(resolver.take_errors().is_empty()); } + +#[test] +fn natives_record_their_registry_slot() { + let mut resolver = NameResolver::new(["len", "+", "len"]); + resolve_source(&mut resolver, "let x = 1; len;").unwrap(); + + // Overloads of one name keep their own slots, in registration order. + let [newest_len, oldest_len] = only_reference(&resolver).0[0][..] else { + panic!("expected both `len` overloads"); + }; + let plus = builtin(&resolver, "+"); + assert_eq!(resolver.native_slots[&oldest_len], 0); + assert_eq!(resolver.native_slots[&plus], 1); + assert_eq!(resolver.native_slots[&newest_len], 2); + // Program symbols aren't natives. + let [x] = declared(&resolver); + assert!(!resolver.native_slots.contains_key(&x)); +}