diff --git a/src/ast/compiler/call_hooks.rs b/src/ast/compiler/call_hooks.rs index e9acc99..b6190a2 100644 --- a/src/ast/compiler/call_hooks.rs +++ b/src/ast/compiler/call_hooks.rs @@ -5,16 +5,10 @@ use crate::ast::diagnostics::Diagnostics; use crate::ast::nodes::{Address, NodeKind, TypedNode, TypedPhase, VirtualId}; use crate::ast::types::StaticType; -/// Gives a call-hook read/write access to the current scope's slot types without -/// exposing `TypeContext` internals. -pub trait TypeSlotAccess { - fn get_slot_type(&self, addr: Address) -> StaticType; - fn set_slot_type(&mut self, addr: Address, ty: StaticType); -} - -/// Gives a call-hook access to the type-checker's core inference operations without -/// depending on the concrete `TypeChecker` type (avoids a circular module dependency). -pub trait TypeInferenceAccess { +/// Gives a call-hook access to the type-checker's core inference operations and +/// scope slot types without depending on the concrete `TypeChecker` type +/// (avoids a circular module dependency). +pub trait InferenceAccess { /// Allocate a fresh unique `TypeVar`. fn fresh_var(&self) -> StaticType; @@ -34,6 +28,11 @@ pub trait TypeInferenceAccess { /// Record-field promotion rule. Returns the promoted record type when one record /// is a strict widening of the other, or `None` otherwise. fn try_record_promote(&self, a: &StaticType, b: &StaticType) -> Option; + + /// Read the raw (unsubstituted) type stored in a scope slot. + /// Used by hooks that need to recover the original `TypeVar` ID after + /// the substitution has already resolved it to a concrete type. + fn get_slot_type(&self, addr: Address) -> StaticType; } /// Maps a concrete type to its element type for index-constraint propagation. @@ -51,8 +50,7 @@ pub type IndexElementFn = fn(&StaticType) -> Option; pub type PostCallHook = fn( args: &TypedNode, ret_ty: StaticType, - inference: &dyn TypeInferenceAccess, - ctx: &mut dyn TypeSlotAccess, + ctx: &dyn InferenceAccess, diag: &mut Diagnostics, ) -> StaticType; diff --git a/src/ast/compiler/type_checker.rs b/src/ast/compiler/type_checker.rs index 610f944..00dc1b2 100644 --- a/src/ast/compiler/type_checker.rs +++ b/src/ast/compiler/type_checker.rs @@ -1,4 +1,4 @@ -use crate::ast::compiler::call_hooks::{CallHooks, IndexElementFn, TypeInferenceAccess, TypeSlotAccess}; +use crate::ast::compiler::call_hooks::{CallHooks, InferenceAccess, IndexElementFn}; use crate::ast::nodes::{ Address, AssignBinding, BoundLike, BoundPhase, DefBinding, IdentifierBinding, LambdaBinding, Node, NodeKind, TypedNode, TypedPhase, VirtualId, @@ -75,39 +75,41 @@ impl<'a> TypeContext<'a> { } } -impl TypeSlotAccess for TypeContext<'_> { - fn get_slot_type(&self, addr: Address) -> StaticType { - self.get_type(addr) - } - - fn set_slot_type(&mut self, addr: Address, ty: StaticType) { - self.set_type(addr, ty); - } +/// Temporary wrapper that gives call-hooks unified access to both the +/// type-checker's inference state and the current scope's slot types. +/// Created on the stack at each hook dispatch site; zero allocation cost. +struct CheckerInferenceAccess<'a, 'b> { + checker: &'a TypeChecker, + ctx: &'b TypeContext<'a>, } -impl TypeInferenceAccess for TypeChecker { +impl InferenceAccess for CheckerInferenceAccess<'_, '_> { fn fresh_var(&self) -> StaticType { - self.fresh_var() + self.checker.fresh_var() } fn unify(&self, a: StaticType, b: StaticType, diag: &mut Diagnostics) { - self.unify(a, b, diag); + self.checker.unify(a, b, diag); } fn bind_typevar(&self, id: u32, ty: StaticType) { - self.bind_var(id, ty); + self.checker.bind_var(id, ty); } fn apply_subst_ty(&self, ty: StaticType) -> StaticType { - Self::apply_subst(ty, &self.subst.borrow()) + TypeChecker::apply_subst(ty, &self.checker.subst.borrow()) } fn try_numeric_widen(&self, a: &StaticType, b: &StaticType) -> Option { - Self::numeric_widen(a, b) + TypeChecker::numeric_widen(a, b) } fn try_record_promote(&self, a: &StaticType, b: &StaticType) -> Option { - Self::record_promote(a, b) + TypeChecker::record_promote(a, b) + } + + fn get_slot_type(&self, addr: Address) -> StaticType { + self.ctx.get_type(addr) } } @@ -1473,7 +1475,8 @@ impl TypeChecker { } = &callee_typed.kind && let Some(hook) = self.call_hooks.get(&idx.0) && let Some(post_call) = hook.post_call { - ret_ty = post_call(&args_typed, ret_ty, self, ctx, diag); + let hook_ctx = CheckerInferenceAccess { checker: self, ctx }; + ret_ty = post_call(&args_typed, ret_ty, &hook_ctx, diag); } ( diff --git a/src/ast/rtl/series/mod.rs b/src/ast/rtl/series/mod.rs index f33d7ba..7a0251f 100644 --- a/src/ast/rtl/series/mod.rs +++ b/src/ast/rtl/series/mod.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use std::rc::Rc; use std::sync::Arc; -use crate::ast::compiler::call_hooks::{FinalizeHook, PostCallHook, TypeInferenceAccess, TypeSlotAccess}; +use crate::ast::compiler::call_hooks::{FinalizeHook, InferenceAccess, PostCallHook}; use crate::ast::diagnostics::Diagnostics; use crate::ast::environment::Environment; use crate::ast::nodes::{IdentifierBinding, Node, NodeKind, TypedNode, TypedPhase}; @@ -36,14 +36,13 @@ pub(crate) fn series_element_type(ty: &StaticType) -> Option { fn series_post_call( _args: &TypedNode, ret_ty: StaticType, - inference: &dyn TypeInferenceAccess, - _ctx: &mut dyn TypeSlotAccess, + ctx: &dyn InferenceAccess, _diag: &mut Diagnostics, ) -> StaticType { if let StaticType::Series(inner) = &ret_ty && **inner == StaticType::Any { - return StaticType::Series(Box::new(inference.fresh_var())); + return StaticType::Series(Box::new(ctx.fresh_var())); } ret_ty } @@ -130,8 +129,7 @@ fn series_finalize( fn push_post_call( args: &TypedNode, ret_ty: StaticType, - inference: &dyn TypeInferenceAccess, - ctx: &mut dyn TypeSlotAccess, + ctx: &dyn InferenceAccess, diag: &mut Diagnostics, ) -> StaticType { let NodeKind::Tuple { elements } = &args.kind else { return ret_ty }; @@ -149,28 +147,27 @@ fn push_post_call( .. } = &series_arg.kind { - // Recover the raw inner type from ctx (before apply_subst) so we can - // find the TypeVar ID even after the first push resolved it. + // Recover the raw inner type from the scope slot (before apply_subst) + // so we can find the TypeVar ID even after the first push resolved it. let raw_inner = match ctx.get_slot_type(*addr) { StaticType::Series(ri) => *ri, _ => inner_ty.clone(), }; - if let Some(promoted) = inference + if let Some(promoted) = ctx .try_numeric_widen(&inner_ty, &val_ty) - .or_else(|| inference.try_record_promote(&inner_ty, &val_ty)) + .or_else(|| ctx.try_record_promote(&inner_ty, &val_ty)) { // Rebind the TypeVar to the promoted type so that finalize // injects the correct schema (e.g. :float instead of :int). if let StaticType::TypeVar(n) = raw_inner { - inference.bind_typevar(n, promoted.clone()); + ctx.bind_typevar(n, promoted.clone()); } - ctx.set_slot_type(*addr, StaticType::Series(Box::new(promoted))); } else { - inference.unify(inner_ty, val_ty, diag); + ctx.unify(inner_ty, val_ty, diag); } } else { - inference.unify(inner_ty, val_ty, diag); + ctx.unify(inner_ty, val_ty, diag); } ret_ty }