Refactor call hooks to use trait objects

This commit refactors the call hook mechanism in the compiler. Instead
of using function pointer types (`PostCallHook`, `FinalizeHook`) and a
struct to hold them (`CallHooks`), it introduces a trait
`RtlCompilerHook` with default implementations for `post_call` and
`finalize`.

This change aligns with Rust's idiomatic way of handling extension
points and allows for more flexible and extensible compiler behavior.
The `TypeChecker` now holds a map of `Rc<dyn RtlCompilerHook>`, enabling
different RTL functions to register their specific compiler hooks. The
`SeriesHook` and `PushHook` structs are introduced to demonstrate this
new pattern.
This commit is contained in:
2026-03-29 14:52:58 +02:00
parent 916460ab03
commit a665e2c1a5
5 changed files with 216 additions and 204 deletions
+35 -26
View File
@@ -5,8 +5,8 @@ use crate::ast::diagnostics::Diagnostics;
use crate::ast::nodes::{Address, NodeKind, TypedNode, TypedPhase, VirtualId};
use crate::ast::types::StaticType;
/// Gives a call-hook access to the type-checker's core inference operations and
/// scope slot types without depending on the concrete `TypeChecker` type
/// Gives a compiler 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`.
@@ -35,29 +35,38 @@ pub trait InferenceAccess {
fn get_slot_type(&self, addr: Address<VirtualId>) -> StaticType;
}
/// Called after the call's return type is resolved.
/// May replace the return type and/or perform unification side-effects (e.g. push).
pub type PostCallHook = fn(
args: &TypedNode,
ret_ty: StaticType,
ctx: &dyn InferenceAccess,
diag: &mut Diagnostics,
) -> StaticType;
/// Called during AST finalization.
/// Returns `Some(new_kind)` to rewrite the node, or `None` to keep the original.
pub type FinalizeHook = fn(
callee: Rc<TypedNode>,
args: Rc<TypedNode>,
node_ty: &StaticType,
subst: &HashMap<u32, StaticType>,
) -> Option<NodeKind<TypedPhase>>;
/// Hooks attached to a specific RTL function slot.
/// Enables type-inference extensions and AST rewrites without hardcoding symbol
/// Extension point that lets RTL functions participate in the compiler's
/// type-inference and AST-finalization passes without hardcoding symbol
/// names anywhere in the compiler core.
#[derive(Default)]
pub struct CallHooks {
pub post_call: Option<PostCallHook>,
pub finalize: Option<FinalizeHook>,
///
/// Each RTL function that needs custom type-level behaviour (e.g. `series`,
/// `push`) implements this trait. Hooks are registered by global slot index
/// during RTL bootstrap and dispatched automatically by the type checker.
///
/// Both methods have no-op defaults so that implementors only override what
/// they need.
pub trait RtlCompilerHook {
/// Called after the call's return type is resolved during type inference.
/// May replace the return type and/or perform unification side-effects.
fn post_call(
&self,
_args: &TypedNode,
ret_ty: StaticType,
_ctx: &dyn InferenceAccess,
_diag: &mut Diagnostics,
) -> StaticType {
ret_ty
}
/// Called during AST finalization.
/// Returns `Some(new_kind)` to rewrite the node, or `None` to keep the original.
fn finalize(
&self,
_callee: Rc<TypedNode>,
_args: Rc<TypedNode>,
_node_ty: &StaticType,
_subst: &HashMap<u32, StaticType>,
) -> Option<NodeKind<TypedPhase>> {
None
}
}
+19 -21
View File
@@ -1,4 +1,4 @@
use crate::ast::compiler::call_hooks::{CallHooks, InferenceAccess};
use crate::ast::compiler::call_hooks::{InferenceAccess, RtlCompilerHook};
use crate::ast::nodes::{
Address, AssignBinding, BoundLike, BoundPhase, DefBinding, IdentifierBinding, LambdaBinding,
Node, NodeKind, TypedNode, TypedPhase, VirtualId,
@@ -184,22 +184,22 @@ pub struct TypeChecker {
/// lazily in `bind_var`: when the callee TypeVar is bound to a concrete type,
/// the element type is extracted directly from the `Series` variant.
index_constraints: std::cell::RefCell<HashMap<u32, u32>>,
/// Type-inference and finalization hooks keyed by global slot index.
/// Compiler hooks keyed by global slot index.
/// Populated from the frozen RTL snapshot; empty for non-RTL call sites.
call_hooks: Rc<HashMap<u32, CallHooks>>,
compiler_hooks: Rc<HashMap<u32, Rc<dyn RtlCompilerHook>>>,
}
impl TypeChecker {
pub fn new(
root_types: Rc<std::cell::RefCell<Vec<StaticType>>>,
call_hooks: Rc<HashMap<u32, CallHooks>>,
compiler_hooks: Rc<HashMap<u32, Rc<dyn RtlCompilerHook>>>,
) -> Self {
Self {
root_types,
var_counter: Cell::new(0),
subst: std::cell::RefCell::new(HashMap::new()),
index_constraints: std::cell::RefCell::new(HashMap::new()),
call_hooks,
compiler_hooks,
}
}
@@ -621,16 +621,15 @@ impl TypeChecker {
binding: IdentifierBinding::Reference(Address::Global(idx)),
..
} = &callee_fin.kind
&& let Some(hook) = self.call_hooks.get(&idx.0)
&& let Some(finalize) = hook.finalize
&& let Some(new_kind) = finalize(
Rc::clone(&callee_fin),
Rc::clone(&args_fin),
node_ty,
subst,
) {
return new_kind;
}
&& let Some(hook) = self.compiler_hooks.get(&idx.0)
&& let Some(new_kind) = hook.finalize(
Rc::clone(&callee_fin),
Rc::clone(&args_fin),
node_ty,
subst,
) {
return new_kind;
}
NodeKind::Call { callee: callee_fin, args: args_fin }
}
@@ -1459,7 +1458,7 @@ impl TypeChecker {
ret_ty = Self::apply_subst(ret_ty, &self.subst.borrow());
}
// Dispatch post-call hooks registered by the RTL (keyed by global slot index).
// Dispatch compiler hooks registered by the RTL (keyed by global slot index).
// Hooks handle type-inference extensions such as:
// - series: inject a fresh TypeVar for the element type
// - push: unify the series element TypeVar with the pushed value type
@@ -1467,11 +1466,10 @@ impl TypeChecker {
binding: IdentifierBinding::Reference(Address::Global(idx)),
..
} = &callee_typed.kind
&& let Some(hook) = self.call_hooks.get(&idx.0)
&& let Some(post_call) = hook.post_call {
let hook_ctx = CheckerInferenceAccess { checker: self, ctx };
ret_ty = post_call(&args_typed, ret_ty, &hook_ctx, diag);
}
&& let Some(hook) = self.compiler_hooks.get(&idx.0) {
let hook_ctx = CheckerInferenceAccess { checker: self, ctx };
ret_ty = hook.post_call(&args_typed, ret_ty, &hook_ctx, diag);
}
(
NodeKind::Call {