Add IndexElementFn for series type probing

Introduce `IndexElementFn` as a type alias for a function that maps a
`StaticType` to an optional `StaticType` representing its element type.
This allows the `TypeChecker` to determine the element type of indexable
types like `Series` without needing to be aware of their specific
implementations.

The `series_element_type` function is created and registered with the
`TypeChecker` to handle `Series` types. This design centralizes
type-specific logic for indexable types, making the `TypeChecker` more
generic and extensible for future indexable types such as `Stream`.
This commit is contained in:
2026-03-29 13:20:49 +02:00
parent 9c434fb49d
commit 93a85cd15c
4 changed files with 44 additions and 22 deletions
+10
View File
@@ -36,6 +36,16 @@ pub trait TypeInferenceAccess {
fn try_record_promote(&self, a: &StaticType, b: &StaticType) -> Option<StaticType>;
}
/// Maps a concrete type to its element type for index-constraint propagation.
/// Called by `bind_var` when a TypeVar with an index constraint is bound.
///
/// Returns `Some(elem_ty)` if `ty` supports integer-index access (e.g. `Series`),
/// or `None` for all non-indexable types.
///
/// Register the appropriate implementation via `TypeChecker::new`. Each indexable
/// type (Series, and in future Stream) contributes a match arm here.
pub type IndexElementFn = fn(&StaticType) -> Option<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(
+15 -19
View File
@@ -1,4 +1,4 @@
use crate::ast::compiler::call_hooks::{CallHooks, TypeInferenceAccess, TypeSlotAccess};
use crate::ast::compiler::call_hooks::{CallHooks, IndexElementFn, TypeInferenceAccess, TypeSlotAccess};
use crate::ast::nodes::{
Address, AssignBinding, BoundLike, BoundPhase, DefBinding, IdentifierBinding, LambdaBinding,
Node, NodeKind, TypedNode, TypedPhase, VirtualId,
@@ -179,12 +179,13 @@ pub struct TypeChecker {
/// When a `TypeVar` is used as a callable with a single `Int` argument —
/// the series lookback pattern `(s 0)` — we record the pairing here instead
/// of eagerly unifying `TypeVar = Series(elem)`. The constraint is evaluated
/// lazily in `bind_var`: only when the callee TypeVar is bound to `Series(inner)`
/// is the result TypeVar also bound to `inner`.
///
/// **Extension point**: other indexable types (e.g. `Stream`, future `Map`)
/// can participate by being matched in `bind_var`'s propagation arm.
/// lazily in `bind_var`: when the callee TypeVar is bound to a concrete type,
/// `index_element_fn` determines the element type (if any) and propagates it.
index_constraints: std::cell::RefCell<HashMap<u32, u32>>,
/// Resolves the element type of an indexable type for index-constraint propagation.
/// Registered at construction; keeps the type-checker core free of type-specific logic.
/// See [`IndexElementFn`] for the contract.
index_element_fn: IndexElementFn,
/// Type-inference and finalization hooks keyed by global slot index.
/// Populated from the frozen RTL snapshot; empty for non-RTL call sites.
call_hooks: Rc<HashMap<u32, CallHooks>>,
@@ -194,12 +195,14 @@ impl TypeChecker {
pub fn new(
root_types: Rc<std::cell::RefCell<Vec<StaticType>>>,
call_hooks: Rc<HashMap<u32, CallHooks>>,
index_element_fn: IndexElementFn,
) -> Self {
Self {
root_types,
var_counter: Cell::new(0),
subst: std::cell::RefCell::new(HashMap::new()),
index_constraints: std::cell::RefCell::new(HashMap::new()),
index_element_fn,
call_hooks,
}
}
@@ -214,22 +217,15 @@ impl TypeChecker {
/// Binds a TypeVar to a concrete type in the substitution, then propagates
/// any pending index-call constraints registered by Step 9.
///
/// When `ty` is `Series(inner)` and `index_constraints[id]` is set, the
/// result TypeVar is also bound to `inner` — connecting the callee TypeVar
/// to its element type without the eager over-constraint of unification.
///
/// To add support for another indexable type (e.g. `Stream`), extend the
/// `match &ty` arm in the propagation block below.
/// If `index_constraints[id]` is set and `index_element_fn` returns an element
/// type for `ty`, the result TypeVar is bound to that element type — connecting
/// the callee TypeVar to its element type without the eager over-constraint of
/// unification. For non-indexable types the result TypeVar stays unresolved.
fn bind_var(&self, id: u32, ty: StaticType) {
self.subst.borrow_mut().insert(id, ty.clone());
let ret_id = self.index_constraints.borrow().get(&id).copied();
if let Some(ret_id) = ret_id {
match &ty {
StaticType::Series(inner) => {
self.subst.borrow_mut().insert(ret_id, *inner.clone());
}
_ => {}
}
if let (Some(ret_id), Some(elem_ty)) = (ret_id, (self.index_element_fn)(&ty)) {
self.subst.borrow_mut().insert(ret_id, elem_ty);
}
}
+4 -3
View File
@@ -23,6 +23,7 @@ use crate::ast::compiler::macros::{MacroEvaluator, MacroExpander, MacroRegistry}
use crate::ast::compiler::optimizer::Optimizer;
use crate::ast::compiler::specializer::{FunctionRegistry, MonoCache, RtlLookupFunc, Specializer};
use crate::ast::rtl::{self, intrinsics};
use crate::ast::rtl::series::series_element_type;
use crate::ast::rtl::docs::{PipelineGenerator, RtlDocEntry, RtlRegistration};
use crate::ast::types::{
CommentLine, NativeFunction, NodeIdentity, Purity, SourceLocation, StaticType, Value,
@@ -132,7 +133,7 @@ impl MacroEvaluator for RuntimeMacroEvaluator {
let (bound_ast, captures, _, _) = Binder::bind_root(initial_scopes, initial_slot_count, self.fixed_scope_idx, node, &mut diag)?;
let bound_ast = CapturePass::apply(bound_ast, &captures);
let checker = TypeChecker::new(self.root_types.clone(), Rc::clone(&self.call_hooks));
let checker = TypeChecker::new(self.root_types.clone(), Rc::clone(&self.call_hooks), series_element_type);
let typed_ast = checker.check(&bound_ast, &[], &mut diag);
if diag.has_errors() {
@@ -480,7 +481,7 @@ impl Environment {
LambdaCollector::collect(&bound_ast, &mut self.function_registry.borrow_mut());
let checker = TypeChecker::new(self.root_types.clone(), Rc::clone(&self.rtl.call_hooks));
let checker = TypeChecker::new(self.root_types.clone(), Rc::clone(&self.rtl.call_hooks), series_element_type);
let wrapped_ast = if let NodeKind::Lambda { .. } = bound_ast.kind {
bound_ast
} else {
@@ -834,7 +835,7 @@ impl Environment {
arg_types: &[StaticType]|
-> Result<(Value, StaticType), String> {
let mut diag = Diagnostics::new();
let checker = TypeChecker::new(root_types.clone(), Rc::clone(&call_hooks));
let checker = TypeChecker::new(root_types.clone(), Rc::clone(&call_hooks), series_element_type);
// Monomorphization: re-type-check the function template with the concrete
// call-site argument types, producing a specialized TypedNode.
+15
View File
@@ -15,6 +15,21 @@ use crate::ast::types::{NativeFunction, NodeIdentity, Purity, Signature, SourceL
// 4. Type-Inference Hooks (registered via RTL bootstrap, no name coupling)
// ============================================================================
/// Index-element hook for the `Series` type.
///
/// Returns the element type of a `Series(inner)`, or `None` for all other types.
/// Registered with `TypeChecker` so that the core type-checker remains agnostic
/// about which concrete types support integer-index access.
///
/// When `Stream` (or future indexable types) is added, extend this function
/// with additional match arms rather than touching the type-checker.
pub(crate) fn series_element_type(ty: &StaticType) -> Option<StaticType> {
match ty {
StaticType::Series(inner) => Some(*inner.clone()),
_ => None,
}
}
/// Post-call hook for `(series n)`.
/// Replaces the `Series(Any)` return type with a fresh TypeVar so that
/// subsequent `push` calls can unify the element type (HM inference).