From 93a85cd15caa3c59bd42e1d20f4bf86b60649e4b Mon Sep 17 00:00:00 2001 From: Brummel Date: Sun, 29 Mar 2026 13:20:49 +0200 Subject: [PATCH] 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`. --- src/ast/compiler/call_hooks.rs | 10 ++++++++++ src/ast/compiler/type_checker.rs | 34 ++++++++++++++------------------ src/ast/environment.rs | 7 ++++--- src/ast/rtl/series/mod.rs | 15 ++++++++++++++ 4 files changed, 44 insertions(+), 22 deletions(-) diff --git a/src/ast/compiler/call_hooks.rs b/src/ast/compiler/call_hooks.rs index cee2405..e9acc99 100644 --- a/src/ast/compiler/call_hooks.rs +++ b/src/ast/compiler/call_hooks.rs @@ -36,6 +36,16 @@ pub trait TypeInferenceAccess { fn try_record_promote(&self, a: &StaticType, b: &StaticType) -> Option; } +/// 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; + /// 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( diff --git a/src/ast/compiler/type_checker.rs b/src/ast/compiler/type_checker.rs index 4608842..610f944 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, 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>, + /// 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>, @@ -194,12 +195,14 @@ impl TypeChecker { pub fn new( root_types: Rc>>, call_hooks: Rc>, + 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); } } diff --git a/src/ast/environment.rs b/src/ast/environment.rs index 29de487..9a15a22 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -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. diff --git a/src/ast/rtl/series/mod.rs b/src/ast/rtl/series/mod.rs index c5d6146..f33d7ba 100644 --- a/src/ast/rtl/series/mod.rs +++ b/src/ast/rtl/series/mod.rs @@ -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 { + 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).