feat: add extern let declarations for JS FFI

Add support for `extern let name: Type` and `extern let name: Type = "jsName"`
syntax for declaring external JavaScript values. This follows the same pattern
as extern fn across all compiler passes: parser, typechecker, interpreter
(runtime error placeholder), JS backend (emits JS name directly without
mangling), formatter, linter, modules, and symbol table.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-20 19:29:44 -05:00
parent 1b629aaae4
commit 667a94b4dc
10 changed files with 161 additions and 2 deletions

View File

@@ -73,6 +73,8 @@ pub struct JsBackend {
used_effects: HashSet<String>,
/// Extern function names mapped to their JS names
extern_fns: HashMap<String, String>,
/// Extern let names mapped to their JS names
extern_lets: HashMap<String, String>,
}
impl JsBackend {
@@ -96,6 +98,7 @@ impl JsBackend {
var_substitutions: HashMap::new(),
used_effects: HashSet::new(),
extern_fns: HashMap::new(),
extern_lets: HashMap::new(),
}
}
@@ -123,6 +126,13 @@ impl JsBackend {
self.extern_fns.insert(ext.name.name.clone(), js_name);
self.functions.insert(ext.name.name.clone());
}
Declaration::ExternLet(ext) => {
let js_name = ext
.js_name
.clone()
.unwrap_or_else(|| ext.name.name.clone());
self.extern_lets.insert(ext.name.name.clone(), js_name);
}
_ => {}
}
}
@@ -1097,6 +1107,9 @@ impl JsBackend {
} else if self.functions.contains(&ident.name) {
// Function reference (used as value)
Ok(self.mangle_name(&ident.name))
} else if let Some(js_name) = self.extern_lets.get(&ident.name) {
// Extern let: use JS name directly (no mangling)
Ok(js_name.clone())
} else {
Ok(self.escape_js_keyword(&ident.name))
}