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:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user