Refactor type variable counter to be shared

This commit is contained in:
2026-03-31 18:06:47 +02:00
parent 7d6e040721
commit 1a4952f055
5 changed files with 82 additions and 9 deletions
@@ -34,6 +34,11 @@ impl TypeChecker {
match ty { match ty {
StaticType::TypeVar(n) => { StaticType::TypeVar(n) => {
if let Some(resolved) = subst.get(&n) { if let Some(resolved) = subst.get(&n) {
// Guard against self-referential substitutions (TypeVar(n) → TypeVar(n))
// which would cause infinite recursion.
if *resolved == StaticType::TypeVar(n) {
return StaticType::TypeVar(n);
}
// Follow the chain (handles transitive substitutions) // Follow the chain (handles transitive substitutions)
Self::apply_subst(resolved.clone(), subst) Self::apply_subst(resolved.clone(), subst)
} else { } else {
+6 -3
View File
@@ -18,8 +18,10 @@ use context::TypeContext;
pub struct TypeChecker { pub struct TypeChecker {
pub(crate) root_types: Rc<std::cell::RefCell<Vec<StaticType>>>, pub(crate) root_types: Rc<std::cell::RefCell<Vec<StaticType>>>,
/// Monotonic counter for generating unique type variable IDs. /// Shared monotonic counter for generating unique type variable IDs.
pub(crate) var_counter: Cell<u32>, /// Points to the `Environment`'s counter so that IDs never collide
/// with TypeVars stored in `root_types` from earlier compilation passes.
pub(crate) var_counter: Rc<Cell<u32>>,
/// Global substitution map: TypeVar ID → resolved StaticType. /// Global substitution map: TypeVar ID → resolved StaticType.
/// Shared across all scopes within a single type-checking pass. /// Shared across all scopes within a single type-checking pass.
pub(crate) subst: std::cell::RefCell<HashMap<u32, StaticType>>, pub(crate) subst: std::cell::RefCell<HashMap<u32, StaticType>>,
@@ -39,11 +41,12 @@ pub struct TypeChecker {
impl TypeChecker { impl TypeChecker {
pub fn new( pub fn new(
root_types: Rc<std::cell::RefCell<Vec<StaticType>>>, root_types: Rc<std::cell::RefCell<Vec<StaticType>>>,
var_counter: Rc<Cell<u32>>,
compiler_hooks: Rc<HashMap<u32, Rc<dyn RtlCompilerHook>>>, compiler_hooks: Rc<HashMap<u32, Rc<dyn RtlCompilerHook>>>,
) -> Self { ) -> Self {
Self { Self {
root_types, root_types,
var_counter: Cell::new(0), var_counter,
subst: std::cell::RefCell::new(HashMap::new()), subst: std::cell::RefCell::new(HashMap::new()),
index_constraints: std::cell::RefCell::new(HashMap::new()), index_constraints: std::cell::RefCell::new(HashMap::new()),
compiler_hooks, compiler_hooks,
+14 -4
View File
@@ -5,7 +5,7 @@ use crate::ast::compiler::{CapturePass, TypeChecker, TypedNode};
use crate::ast::nodes::{AnalyzedPhase, BoundPhase, Symbol, SyntaxKind, SyntaxNode}; use crate::ast::nodes::{AnalyzedPhase, BoundPhase, Symbol, SyntaxKind, SyntaxNode};
use crate::ast::parser::Parser; use crate::ast::parser::Parser;
use crate::ast::vm::{GlobalStore, TracingObserver, VM}; use crate::ast::vm::{GlobalStore, TracingObserver, VM};
use std::cell::RefCell; use std::cell::{Cell, RefCell};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::rc::Rc; use std::rc::Rc;
@@ -71,6 +71,11 @@ pub struct Environment {
pub function_registry: Rc<RefCell<GlobalFunctionRegistry>>, pub function_registry: Rc<RefCell<GlobalFunctionRegistry>>,
pub typed_function_registry: Rc<RefCell<GlobalAnalyzedRegistry>>, pub typed_function_registry: Rc<RefCell<GlobalAnalyzedRegistry>>,
pub monomorph_cache: Rc<RefCell<MonoCache>>, pub monomorph_cache: Rc<RefCell<MonoCache>>,
/// Shared monotonic counter for generating unique HM type-variable IDs.
/// Survives across multiple `compile()` / `run_script()` calls so that
/// TypeVar IDs created in one pass never collide with IDs stored in
/// `root_types` from earlier passes (prevents `Forall` self-substitution loops).
pub(crate) typevar_counter: Rc<Cell<u32>>,
pub debug_mode: bool, pub debug_mode: bool,
pub optimization: bool, pub optimization: bool,
pub macro_registry: Rc<RefCell<MacroRegistry>>, pub macro_registry: Rc<RefCell<MacroRegistry>>,
@@ -106,6 +111,7 @@ struct RuntimeMacroEvaluator {
root_scopes: Rc<RefCell<Vec<CompilerScope>>>, root_scopes: Rc<RefCell<Vec<CompilerScope>>>,
root_slot_count: Rc<RefCell<u32>>, root_slot_count: Rc<RefCell<u32>>,
root_types: Rc<RefCell<Vec<StaticType>>>, root_types: Rc<RefCell<Vec<StaticType>>>,
typevar_counter: Rc<Cell<u32>>,
globals: GlobalStore, globals: GlobalStore,
root_purity: Rc<RefCell<Vec<Purity>>>, root_purity: Rc<RefCell<Vec<Purity>>>,
compiler_hooks: Rc<HashMap<u32, Rc<dyn RtlCompilerHook>>>, compiler_hooks: Rc<HashMap<u32, Rc<dyn RtlCompilerHook>>>,
@@ -129,7 +135,7 @@ impl MacroEvaluator for RuntimeMacroEvaluator {
let (bound_ast, captures, _, _) = Binder::bind_root(initial_scopes, initial_slot_count, node, &mut diag)?; let (bound_ast, captures, _, _) = Binder::bind_root(initial_scopes, initial_slot_count, node, &mut diag)?;
let bound_ast = CapturePass::apply(bound_ast, &captures); let bound_ast = CapturePass::apply(bound_ast, &captures);
let checker = TypeChecker::new(self.root_types.clone(), Rc::clone(&self.compiler_hooks)); let checker = TypeChecker::new(self.root_types.clone(), Rc::clone(&self.typevar_counter), Rc::clone(&self.compiler_hooks));
let typed_ast = checker.check(&bound_ast, &[], &mut diag); let typed_ast = checker.check(&bound_ast, &[], &mut diag);
if diag.has_errors() { if diag.has_errors() {
@@ -162,6 +168,7 @@ impl Environment {
function_registry: Rc::new(RefCell::new(HashMap::new())), function_registry: Rc::new(RefCell::new(HashMap::new())),
typed_function_registry: Rc::new(RefCell::new(HashMap::new())), typed_function_registry: Rc::new(RefCell::new(HashMap::new())),
monomorph_cache: Rc::new(RefCell::new(HashMap::new())), monomorph_cache: Rc::new(RefCell::new(HashMap::new())),
typevar_counter: Rc::new(Cell::new(0)),
debug_mode: false, debug_mode: false,
optimization: true, optimization: true,
macro_registry: Rc::new(RefCell::new(MacroRegistry::new())), macro_registry: Rc::new(RefCell::new(MacroRegistry::new())),
@@ -254,6 +261,7 @@ impl Environment {
function_registry: Rc::new(RefCell::new(HashMap::new())), function_registry: Rc::new(RefCell::new(HashMap::new())),
typed_function_registry: Rc::new(RefCell::new(HashMap::new())), typed_function_registry: Rc::new(RefCell::new(HashMap::new())),
monomorph_cache: Rc::new(RefCell::new(HashMap::new())), monomorph_cache: Rc::new(RefCell::new(HashMap::new())),
typevar_counter: Rc::new(Cell::new(0)),
debug_mode: false, debug_mode: false,
optimization: true, optimization: true,
macro_registry: Rc::new(RefCell::new(MacroRegistry::new())), macro_registry: Rc::new(RefCell::new(MacroRegistry::new())),
@@ -321,6 +329,7 @@ impl Environment {
root_scopes: self.root_scopes.clone(), root_scopes: self.root_scopes.clone(),
root_slot_count: self.root_slot_count.clone(), root_slot_count: self.root_slot_count.clone(),
root_types: self.root_types.clone(), root_types: self.root_types.clone(),
typevar_counter: Rc::clone(&self.typevar_counter),
globals: self.global_store(), globals: self.global_store(),
root_purity: self.root_purity.clone(), root_purity: self.root_purity.clone(),
compiler_hooks: Rc::clone(&self.rtl.compiler_hooks), compiler_hooks: Rc::clone(&self.rtl.compiler_hooks),
@@ -447,7 +456,7 @@ impl Environment {
/// Type-checks a bound AST and collects doc comments. /// Type-checks a bound AST and collects doc comments.
fn type_check(&self, bound_ast: &Node<BoundPhase>, diagnostics: &mut Diagnostics) -> TypedNode { fn type_check(&self, bound_ast: &Node<BoundPhase>, diagnostics: &mut Diagnostics) -> TypedNode {
let checker = TypeChecker::new(self.root_types.clone(), Rc::clone(&self.rtl.compiler_hooks)); let checker = TypeChecker::new(self.root_types.clone(), Rc::clone(&self.typevar_counter), Rc::clone(&self.rtl.compiler_hooks));
let typed = checker.check(bound_ast, &[], diagnostics); let typed = checker.check(bound_ast, &[], diagnostics);
self.collect_doc_comments(&typed); self.collect_doc_comments(&typed);
typed typed
@@ -766,13 +775,14 @@ impl Environment {
let root_purity = self.root_purity.clone(); let root_purity = self.root_purity.clone();
let optimization = self.optimization; let optimization = self.optimization;
let compiler_hooks = Rc::clone(&self.rtl.compiler_hooks); let compiler_hooks = Rc::clone(&self.rtl.compiler_hooks);
let typevar_counter = Rc::clone(&self.typevar_counter);
let compiler = Rc::new( let compiler = Rc::new(
move |func_template: Rc<Node<AnalyzedPhase>>, move |func_template: Rc<Node<AnalyzedPhase>>,
arg_types: &[StaticType]| arg_types: &[StaticType]|
-> Result<(Value, StaticType), String> { -> Result<(Value, StaticType), String> {
let mut diag = Diagnostics::new(); let mut diag = Diagnostics::new();
let checker = TypeChecker::new(root_types.clone(), Rc::clone(&compiler_hooks)); let checker = TypeChecker::new(root_types.clone(), Rc::clone(&typevar_counter), Rc::clone(&compiler_hooks));
// Monomorphization: re-type-check the function template with the concrete // Monomorphization: re-type-check the function template with the concrete
// call-site argument types, producing a specialized TypedNode. // call-site argument types, producing a specialized TypedNode.
-2
View File
@@ -1,8 +1,6 @@
;; Myc System Library ;; Myc System Library
;; Standard macros and core utilities. ;; Standard macros and core utilities.
(def func1 (fn [] (print "Hello World")))
(macro while [cond body] (macro while [cond body]
`((fn [] (if ~cond `((fn [] (if ~cond
(do ~body (again)) (do ~body (again))
+57
View File
@@ -0,0 +1,57 @@
use myc::ast::environment::Environment;
use myc::ast::types::Value;
#[test]
fn repl_preserves_variable_bindings() {
let env = Environment::new();
let r1 = env.run_script("(def x 42)");
assert!(r1.is_ok(), "def should succeed: {:?}", r1.err());
let r2 = env.run_script("(+ x 1)");
assert_eq!(r2.unwrap(), Value::Int(43));
}
#[test]
fn repl_preserves_function_bindings() {
let env = Environment::new();
let r1 = env.run_script("(def double (fn [n] (* n 2)))");
assert!(r1.is_ok(), "def fn should succeed: {:?}", r1.err());
let r2 = env.run_script("(double 21)");
assert_eq!(r2.unwrap(), Value::Int(42));
}
#[test]
fn repl_function_uses_prior_variable() {
let env = Environment::new();
env.run_script("(def x 10)").unwrap();
env.run_script("(def double (fn [n] (* n 2)))").unwrap();
let result = env.run_script("(double x)");
assert_eq!(result.unwrap(), Value::Int(20));
}
#[test]
fn repl_reassign_variable() {
let env = Environment::new();
env.run_script("(def x 1)").unwrap();
env.run_script("(assign x 99)").unwrap();
let result = env.run_script("x");
assert_eq!(result.unwrap(), Value::Int(99));
}
#[test]
fn repl_macro_from_prior_call() {
let env = Environment::new();
env.run_script("(macro unless [c body] `(if ~c ... ~body))")
.unwrap();
let result = env.run_script(r#"(unless false "yes")"#);
assert_eq!(result.unwrap(), Value::Text("yes".into()));
}