Refactor InferenceAccess for call hooks
The `TypeInferenceAccess` and `TypeSlotAccess` traits have been consolidated into a single `InferenceAccess` trait. This simplifies the call hook interface by providing a unified way to access type inference operations and scope slot types. A new struct, `CheckerInferenceAccess`, is introduced to provide a concrete implementation of `InferenceAccess` for the `TypeChecker`. This struct wraps both the `TypeChecker` and the `TypeContext`, allowing call hooks to interact with both without needing to know their specific types. This avoids potential circular dependencies between modules. The `get_slot_type` method has been moved to the new `InferenceAccess` trait. This method is crucial for call hooks that need to recover the original `TypeVar` ID after substitutions have occurred, particularly in the `push_post_call` function for series.
This commit is contained in:
@@ -5,16 +5,10 @@ use crate::ast::diagnostics::Diagnostics;
|
|||||||
use crate::ast::nodes::{Address, NodeKind, TypedNode, TypedPhase, VirtualId};
|
use crate::ast::nodes::{Address, NodeKind, TypedNode, TypedPhase, VirtualId};
|
||||||
use crate::ast::types::StaticType;
|
use crate::ast::types::StaticType;
|
||||||
|
|
||||||
/// Gives a call-hook read/write access to the current scope's slot types without
|
/// Gives a call-hook access to the type-checker's core inference operations and
|
||||||
/// exposing `TypeContext` internals.
|
/// scope slot types without depending on the concrete `TypeChecker` type
|
||||||
pub trait TypeSlotAccess {
|
/// (avoids a circular module dependency).
|
||||||
fn get_slot_type(&self, addr: Address<VirtualId>) -> StaticType;
|
pub trait InferenceAccess {
|
||||||
fn set_slot_type(&mut self, addr: Address<VirtualId>, 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 {
|
|
||||||
/// Allocate a fresh unique `TypeVar`.
|
/// Allocate a fresh unique `TypeVar`.
|
||||||
fn fresh_var(&self) -> StaticType;
|
fn fresh_var(&self) -> StaticType;
|
||||||
|
|
||||||
@@ -34,6 +28,11 @@ pub trait TypeInferenceAccess {
|
|||||||
/// Record-field promotion rule. Returns the promoted record type when one record
|
/// Record-field promotion rule. Returns the promoted record type when one record
|
||||||
/// is a strict widening of the other, or `None` otherwise.
|
/// is a strict widening of the other, or `None` otherwise.
|
||||||
fn try_record_promote(&self, a: &StaticType, b: &StaticType) -> Option<StaticType>;
|
fn try_record_promote(&self, a: &StaticType, b: &StaticType) -> Option<StaticType>;
|
||||||
|
|
||||||
|
/// 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<VirtualId>) -> StaticType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maps a concrete type to its element type for index-constraint propagation.
|
/// Maps a concrete type to its element type for index-constraint propagation.
|
||||||
@@ -51,8 +50,7 @@ pub type IndexElementFn = fn(&StaticType) -> Option<StaticType>;
|
|||||||
pub type PostCallHook = fn(
|
pub type PostCallHook = fn(
|
||||||
args: &TypedNode,
|
args: &TypedNode,
|
||||||
ret_ty: StaticType,
|
ret_ty: StaticType,
|
||||||
inference: &dyn TypeInferenceAccess,
|
ctx: &dyn InferenceAccess,
|
||||||
ctx: &mut dyn TypeSlotAccess,
|
|
||||||
diag: &mut Diagnostics,
|
diag: &mut Diagnostics,
|
||||||
) -> StaticType;
|
) -> StaticType;
|
||||||
|
|
||||||
|
|||||||
@@ -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::{
|
use crate::ast::nodes::{
|
||||||
Address, AssignBinding, BoundLike, BoundPhase, DefBinding, IdentifierBinding, LambdaBinding,
|
Address, AssignBinding, BoundLike, BoundPhase, DefBinding, IdentifierBinding, LambdaBinding,
|
||||||
Node, NodeKind, TypedNode, TypedPhase, VirtualId,
|
Node, NodeKind, TypedNode, TypedPhase, VirtualId,
|
||||||
@@ -75,39 +75,41 @@ impl<'a> TypeContext<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypeSlotAccess for TypeContext<'_> {
|
/// Temporary wrapper that gives call-hooks unified access to both the
|
||||||
fn get_slot_type(&self, addr: Address<VirtualId>) -> StaticType {
|
/// type-checker's inference state and the current scope's slot types.
|
||||||
self.get_type(addr)
|
/// Created on the stack at each hook dispatch site; zero allocation cost.
|
||||||
}
|
struct CheckerInferenceAccess<'a, 'b> {
|
||||||
|
checker: &'a TypeChecker,
|
||||||
fn set_slot_type(&mut self, addr: Address<VirtualId>, ty: StaticType) {
|
ctx: &'b TypeContext<'a>,
|
||||||
self.set_type(addr, ty);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypeInferenceAccess for TypeChecker {
|
impl InferenceAccess for CheckerInferenceAccess<'_, '_> {
|
||||||
fn fresh_var(&self) -> StaticType {
|
fn fresh_var(&self) -> StaticType {
|
||||||
self.fresh_var()
|
self.checker.fresh_var()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unify(&self, a: StaticType, b: StaticType, diag: &mut Diagnostics) {
|
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) {
|
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 {
|
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<StaticType> {
|
fn try_numeric_widen(&self, a: &StaticType, b: &StaticType) -> Option<StaticType> {
|
||||||
Self::numeric_widen(a, b)
|
TypeChecker::numeric_widen(a, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_record_promote(&self, a: &StaticType, b: &StaticType) -> Option<StaticType> {
|
fn try_record_promote(&self, a: &StaticType, b: &StaticType) -> Option<StaticType> {
|
||||||
Self::record_promote(a, b)
|
TypeChecker::record_promote(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_slot_type(&self, addr: Address<VirtualId>) -> StaticType {
|
||||||
|
self.ctx.get_type(addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1473,7 +1475,8 @@ impl TypeChecker {
|
|||||||
} = &callee_typed.kind
|
} = &callee_typed.kind
|
||||||
&& let Some(hook) = self.call_hooks.get(&idx.0)
|
&& let Some(hook) = self.call_hooks.get(&idx.0)
|
||||||
&& let Some(post_call) = hook.post_call {
|
&& 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
(
|
(
|
||||||
|
|||||||
+13
-16
@@ -5,7 +5,7 @@ use std::collections::HashMap;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
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::diagnostics::Diagnostics;
|
||||||
use crate::ast::environment::Environment;
|
use crate::ast::environment::Environment;
|
||||||
use crate::ast::nodes::{IdentifierBinding, Node, NodeKind, TypedNode, TypedPhase};
|
use crate::ast::nodes::{IdentifierBinding, Node, NodeKind, TypedNode, TypedPhase};
|
||||||
@@ -36,14 +36,13 @@ pub(crate) fn series_element_type(ty: &StaticType) -> Option<StaticType> {
|
|||||||
fn series_post_call(
|
fn series_post_call(
|
||||||
_args: &TypedNode,
|
_args: &TypedNode,
|
||||||
ret_ty: StaticType,
|
ret_ty: StaticType,
|
||||||
inference: &dyn TypeInferenceAccess,
|
ctx: &dyn InferenceAccess,
|
||||||
_ctx: &mut dyn TypeSlotAccess,
|
|
||||||
_diag: &mut Diagnostics,
|
_diag: &mut Diagnostics,
|
||||||
) -> StaticType {
|
) -> StaticType {
|
||||||
if let StaticType::Series(inner) = &ret_ty
|
if let StaticType::Series(inner) = &ret_ty
|
||||||
&& **inner == StaticType::Any
|
&& **inner == StaticType::Any
|
||||||
{
|
{
|
||||||
return StaticType::Series(Box::new(inference.fresh_var()));
|
return StaticType::Series(Box::new(ctx.fresh_var()));
|
||||||
}
|
}
|
||||||
ret_ty
|
ret_ty
|
||||||
}
|
}
|
||||||
@@ -130,8 +129,7 @@ fn series_finalize(
|
|||||||
fn push_post_call(
|
fn push_post_call(
|
||||||
args: &TypedNode,
|
args: &TypedNode,
|
||||||
ret_ty: StaticType,
|
ret_ty: StaticType,
|
||||||
inference: &dyn TypeInferenceAccess,
|
ctx: &dyn InferenceAccess,
|
||||||
ctx: &mut dyn TypeSlotAccess,
|
|
||||||
diag: &mut Diagnostics,
|
diag: &mut Diagnostics,
|
||||||
) -> StaticType {
|
) -> StaticType {
|
||||||
let NodeKind::Tuple { elements } = &args.kind else { return ret_ty };
|
let NodeKind::Tuple { elements } = &args.kind else { return ret_ty };
|
||||||
@@ -149,28 +147,27 @@ fn push_post_call(
|
|||||||
..
|
..
|
||||||
} = &series_arg.kind
|
} = &series_arg.kind
|
||||||
{
|
{
|
||||||
// Recover the raw inner type from ctx (before apply_subst) so we can
|
// Recover the raw inner type from the scope slot (before apply_subst)
|
||||||
// find the TypeVar ID even after the first push resolved it.
|
// so we can find the TypeVar ID even after the first push resolved it.
|
||||||
let raw_inner = match ctx.get_slot_type(*addr) {
|
let raw_inner = match ctx.get_slot_type(*addr) {
|
||||||
StaticType::Series(ri) => *ri,
|
StaticType::Series(ri) => *ri,
|
||||||
_ => inner_ty.clone(),
|
_ => inner_ty.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(promoted) = inference
|
if let Some(promoted) = ctx
|
||||||
.try_numeric_widen(&inner_ty, &val_ty)
|
.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
|
// Rebind the TypeVar to the promoted type so that finalize
|
||||||
// injects the correct schema (e.g. :float instead of :int).
|
// injects the correct schema (e.g. :float instead of :int).
|
||||||
if let StaticType::TypeVar(n) = raw_inner {
|
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);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
inference.unify(inner_ty, val_ty, diag);
|
ctx.unify(inner_ty, val_ty, diag);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.unify(inner_ty, val_ty, diag);
|
||||||
}
|
}
|
||||||
ret_ty
|
ret_ty
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user