use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use shape_ast::ast::TypeAnnotation;
use shape_runtime::type_schema::{FieldType, SchemaId, TypeSchema, TypeSchemaRegistry};
use shape_runtime::type_system::{BuiltinTypes, StorageType};
use shape_value::v2::struct_layout::{FieldKind, StructLayout};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NumericType {
Int,
IntWidth(shape_ast::IntWidth),
Number,
Decimal,
}
pub use shape_value::NativeKind;
pub type StorageHint = NativeKind;
pub fn native_kind_from_storage_type(st: &StorageType) -> Option<NativeKind> {
match st {
StorageType::Float64 => Some(NativeKind::Float64),
StorageType::Int64 => Some(NativeKind::Int64),
StorageType::Bool => Some(NativeKind::Bool),
StorageType::String => Some(NativeKind::String),
StorageType::NullableFloat64 => Some(NativeKind::NullableFloat64),
StorageType::NullableInt64 => Some(NativeKind::NullableInt64),
StorageType::NullableBool => Some(NativeKind::Bool),
StorageType::Array(_)
| StorageType::Table { .. }
| StorageType::Object
| StorageType::Result { .. }
| StorageType::TaggedUnion { .. }
| StorageType::Function
| StorageType::Struct(_)
| StorageType::Dynamic => None,
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FrameDescriptor {
pub slots: Vec<NativeKind>,
#[serde(default)]
pub return_kind: Option<NativeKind>,
}
impl FrameDescriptor {
pub fn new() -> Self {
Self {
slots: Vec::new(),
return_kind: None,
}
}
pub fn from_slots(slots: Vec<NativeKind>) -> Self {
Self {
slots,
return_kind: None,
}
}
#[inline]
pub fn len(&self) -> usize {
self.slots.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.slots.is_empty()
}
#[inline]
pub fn slot(&self, index: usize) -> Option<NativeKind> {
self.slots.get(index).copied()
}
}
impl Default for FrameDescriptor {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum VariableKind {
Value,
Table { element_type: String },
RowView { element_type: String },
Column {
element_type: String,
column_type: String,
},
Indexed {
element_type: String,
index_column: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BindingOwnershipClass {
OwnedImmutable,
OwnedMutable,
Flexible,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Aliasability {
Unique,
SharedImmutable,
SharedMutable,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MutationCapability {
Immutable,
LocalMutable,
SharedMutable,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum EscapeStatus {
Local,
Captured,
Escaped,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BindingStorageClass {
Deferred,
Direct,
UniqueHeap,
SharedCow,
Reference,
LocalMutablePtr,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct BindingSemantics {
pub ownership_class: BindingOwnershipClass,
pub storage_class: BindingStorageClass,
pub aliasability: Aliasability,
pub mutation_capability: MutationCapability,
pub escape_status: EscapeStatus,
pub return_ownership_hint: Option<crate::mir::ReturnOwnershipMode>,
}
impl BindingSemantics {
pub const fn deferred(ownership_class: BindingOwnershipClass) -> Self {
Self {
ownership_class,
storage_class: BindingStorageClass::Deferred,
aliasability: Aliasability::Unique,
mutation_capability: match ownership_class {
BindingOwnershipClass::OwnedImmutable => MutationCapability::Immutable,
BindingOwnershipClass::OwnedMutable => MutationCapability::LocalMutable,
BindingOwnershipClass::Flexible => MutationCapability::SharedMutable,
},
escape_status: EscapeStatus::Local,
return_ownership_hint: None,
}
}
}
#[derive(Debug, Clone)]
pub struct VariableTypeInfo {
pub schema_id: Option<SchemaId>,
pub type_name: Option<String>,
pub is_definite: bool,
pub storage_hint: Option<StorageHint>,
pub concrete_numeric_type: Option<String>,
pub kind: VariableKind,
pub v2_array_element_kind: Option<FieldKind>,
pub v2_struct_layout: Option<SchemaId>,
}
impl VariableTypeInfo {
pub fn known(schema_id: SchemaId, type_name: String) -> Self {
let concrete_numeric_type = Self::infer_numeric_runtime_name(&type_name);
Self {
schema_id: Some(schema_id),
type_name: Some(type_name),
is_definite: true,
storage_hint: None,
concrete_numeric_type,
kind: VariableKind::Value,
v2_array_element_kind: None,
v2_struct_layout: None,
}
}
pub fn unknown() -> Self {
Self {
schema_id: None,
type_name: None,
is_definite: false,
storage_hint: None,
concrete_numeric_type: None,
kind: VariableKind::Value,
v2_array_element_kind: None,
v2_struct_layout: None,
}
}
pub fn named(type_name: String) -> Self {
let storage_hint = Self::infer_storage_hint(&type_name);
let concrete_numeric_type = Self::infer_numeric_runtime_name(&type_name);
Self {
schema_id: None,
type_name: Some(type_name),
is_definite: false,
storage_hint,
concrete_numeric_type,
kind: VariableKind::Value,
v2_array_element_kind: None,
v2_struct_layout: None,
}
}
pub fn with_storage(type_name: String, storage_hint: StorageHint) -> Self {
let concrete_numeric_type = Self::infer_numeric_runtime_name(&type_name);
Self {
schema_id: None,
type_name: Some(type_name),
is_definite: true,
storage_hint: Some(storage_hint),
concrete_numeric_type,
kind: VariableKind::Value,
v2_array_element_kind: None,
v2_struct_layout: None,
}
}
pub fn nullable_number() -> Self {
Self {
schema_id: None,
type_name: Some("Option<Number>".to_string()),
is_definite: true,
storage_hint: Some(StorageHint::NullableFloat64),
concrete_numeric_type: Some("f64".to_string()),
kind: VariableKind::Value,
v2_array_element_kind: None,
v2_struct_layout: None,
}
}
pub fn number() -> Self {
Self {
schema_id: None,
type_name: Some("Number".to_string()),
is_definite: true,
storage_hint: Some(StorageHint::Float64),
concrete_numeric_type: Some("f64".to_string()),
kind: VariableKind::Value,
v2_array_element_kind: None,
v2_struct_layout: None,
}
}
pub fn row_view(schema_id: SchemaId, type_name: String) -> Self {
Self {
schema_id: Some(schema_id),
type_name: Some(type_name.clone()),
is_definite: true,
storage_hint: None,
concrete_numeric_type: None,
kind: VariableKind::RowView {
element_type: type_name,
},
v2_array_element_kind: None,
v2_struct_layout: None,
}
}
pub fn datatable(schema_id: SchemaId, type_name: String) -> Self {
Self {
schema_id: Some(schema_id),
type_name: Some(type_name.clone()),
is_definite: true,
storage_hint: None,
concrete_numeric_type: None,
kind: VariableKind::Table {
element_type: type_name,
},
v2_array_element_kind: None,
v2_struct_layout: None,
}
}
pub fn column(schema_id: SchemaId, type_name: String, element_type: String) -> Self {
Self {
schema_id: Some(schema_id),
type_name: Some(type_name.clone()),
is_definite: true,
storage_hint: None,
concrete_numeric_type: None,
kind: VariableKind::Column {
element_type,
column_type: type_name,
},
v2_array_element_kind: None,
v2_struct_layout: None,
}
}
pub fn indexed(schema_id: SchemaId, type_name: String, index_column: String) -> Self {
Self {
schema_id: Some(schema_id),
type_name: Some(type_name.clone()),
is_definite: true,
storage_hint: None,
concrete_numeric_type: None,
kind: VariableKind::Indexed {
element_type: type_name,
index_column,
},
v2_array_element_kind: None,
v2_struct_layout: None,
}
}
pub fn is_known(&self) -> bool {
self.schema_id.is_some()
}
pub fn uses_nan_sentinel(&self) -> bool {
self.storage_hint == Some(StorageHint::NullableFloat64)
}
pub fn is_datatable(&self) -> bool {
matches!(self.kind, VariableKind::Table { .. })
}
pub fn is_row_view(&self) -> bool {
matches!(self.kind, VariableKind::RowView { .. })
}
pub fn is_column(&self) -> bool {
matches!(self.kind, VariableKind::Column { .. })
}
pub fn is_indexed(&self) -> bool {
matches!(self.kind, VariableKind::Indexed { .. })
}
fn infer_storage_hint(type_name: &str) -> Option<StorageHint> {
let trimmed = type_name.trim();
if let Some(inner) = Self::option_inner_type(trimmed) {
let inner = inner.trim();
if let Some(runtime) = BuiltinTypes::canonical_numeric_runtime_name(inner)
&& let Some(hint) = Self::storage_hint_for_runtime_numeric(runtime, true)
{
return Some(hint);
}
if BuiltinTypes::is_bool_type_name(inner) {
return Some(StorageHint::Bool);
}
if BuiltinTypes::is_string_type_name(inner) {
return Some(StorageHint::String);
}
return None;
}
if let Some(runtime) = BuiltinTypes::canonical_numeric_runtime_name(trimmed)
&& let Some(hint) = Self::storage_hint_for_runtime_numeric(runtime, false)
{
return Some(hint);
}
if BuiltinTypes::is_bool_type_name(trimmed) {
return Some(StorageHint::Bool);
}
if BuiltinTypes::is_string_type_name(trimmed) {
return Some(StorageHint::String);
}
None
}
fn option_inner_type(type_name: &str) -> Option<&str> {
type_name
.strip_prefix("Option<")
.and_then(|inner| inner.strip_suffix('>'))
}
fn storage_hint_for_runtime_numeric(runtime_name: &str, nullable: bool) -> Option<StorageHint> {
let base = match runtime_name {
"f32" | "f64" => StorageHint::Float64,
"i8" => StorageHint::Int8,
"u8" => StorageHint::UInt8,
"i16" => StorageHint::Int16,
"u16" => StorageHint::UInt16,
"i32" => StorageHint::Int32,
"u32" => StorageHint::UInt32,
"i64" => StorageHint::Int64,
"u64" => StorageHint::UInt64,
"isize" => StorageHint::IntSize,
"usize" => StorageHint::UIntSize,
_ => return None,
};
Some(base.with_nullability(nullable))
}
fn infer_numeric_runtime_name(type_name: &str) -> Option<String> {
let inner = if type_name.starts_with("Option<") && type_name.ends_with('>') {
&type_name["Option<".len()..type_name.len() - 1]
} else {
type_name
};
BuiltinTypes::canonical_numeric_runtime_name(inner).map(ToString::to_string)
}
}
#[derive(Debug, Clone)]
pub struct LocalTypesSnapshot {
pub local_types: HashMap<u16, VariableTypeInfo>,
pub local_type_scopes: Vec<HashMap<u16, VariableTypeInfo>>,
}
impl LocalTypesSnapshot {
pub fn len(&self) -> usize {
self.local_types.len()
}
pub fn is_empty(&self) -> bool {
self.local_types.is_empty()
}
}
#[derive(Debug)]
pub struct TypeTracker {
schema_registry: TypeSchemaRegistry,
local_types: HashMap<u16, VariableTypeInfo>,
binding_types: HashMap<u16, VariableTypeInfo>,
local_binding_semantics: HashMap<u16, BindingSemantics>,
binding_semantics: HashMap<u16, BindingSemantics>,
local_type_scopes: Vec<HashMap<u16, VariableTypeInfo>>,
local_binding_semantic_scopes: Vec<HashMap<u16, BindingSemantics>>,
function_return_types: HashMap<String, String>,
object_field_contracts: HashMap<SchemaId, HashMap<String, TypeAnnotation>>,
pub v2_layouts: HashMap<SchemaId, StructLayout>,
inline_object_counter: u64,
}
impl TypeTracker {
pub fn new(schema_registry: TypeSchemaRegistry) -> Self {
Self {
schema_registry,
local_types: HashMap::new(),
binding_types: HashMap::new(),
local_binding_semantics: HashMap::new(),
binding_semantics: HashMap::new(),
local_type_scopes: vec![HashMap::new()],
local_binding_semantic_scopes: vec![HashMap::new()],
function_return_types: HashMap::new(),
object_field_contracts: HashMap::new(),
v2_layouts: HashMap::new(),
inline_object_counter: 0,
}
}
pub fn empty() -> Self {
Self::new(TypeSchemaRegistry::new())
}
pub fn with_stdlib() -> Self {
Self::new(TypeSchemaRegistry::with_stdlib_types())
}
pub fn schema_registry(&self) -> &TypeSchemaRegistry {
&self.schema_registry
}
pub fn schema_registry_mut(&mut self) -> &mut TypeSchemaRegistry {
&mut self.schema_registry
}
pub fn push_scope(&mut self) {
self.local_type_scopes.push(HashMap::new());
self.local_binding_semantic_scopes.push(HashMap::new());
}
pub fn pop_scope(&mut self) {
if let Some(scope) = self.local_type_scopes.pop() {
for slot in scope.keys() {
self.local_types.remove(slot);
}
}
if let Some(scope) = self.local_binding_semantic_scopes.pop() {
for slot in scope.keys() {
self.local_binding_semantics.remove(slot);
}
}
}
pub fn set_local_type(&mut self, slot: u16, type_info: VariableTypeInfo) {
let resolved_info = if type_info.type_name.is_some() && type_info.schema_id.is_none() {
self.resolve_type_info(type_info)
} else {
type_info
};
if let Some(scope) = self.local_type_scopes.last_mut() {
scope.insert(slot, resolved_info.clone());
}
self.local_types.insert(slot, resolved_info);
}
pub fn set_binding_type(&mut self, slot: u16, type_info: VariableTypeInfo) {
let resolved_info = if type_info.type_name.is_some() && type_info.schema_id.is_none() {
self.resolve_type_info(type_info)
} else {
type_info
};
self.binding_types.insert(slot, resolved_info);
}
pub fn set_local_binding_semantics(&mut self, slot: u16, semantics: BindingSemantics) {
if let Some(scope) = self.local_binding_semantic_scopes.last_mut() {
scope.insert(slot, semantics);
}
self.local_binding_semantics.insert(slot, semantics);
}
pub fn set_binding_semantics(&mut self, slot: u16, semantics: BindingSemantics) {
self.binding_semantics.insert(slot, semantics);
}
pub fn set_local_binding_storage_class(
&mut self,
slot: u16,
storage_class: BindingStorageClass,
) {
if let Some(existing) = self.local_binding_semantics.get_mut(&slot) {
existing.storage_class = storage_class;
}
for scope in self.local_binding_semantic_scopes.iter_mut().rev() {
if let Some(existing) = scope.get_mut(&slot) {
existing.storage_class = storage_class;
break;
}
}
}
pub fn set_binding_storage_class(&mut self, slot: u16, storage_class: BindingStorageClass) {
if let Some(existing) = self.binding_semantics.get_mut(&slot) {
existing.storage_class = storage_class;
}
}
pub fn get_local_type(&self, slot: u16) -> Option<&VariableTypeInfo> {
self.local_types.get(&slot)
}
pub fn get_binding_type(&self, slot: u16) -> Option<&VariableTypeInfo> {
self.binding_types.get(&slot)
}
pub fn get_local_binding_semantics(&self, slot: u16) -> Option<&BindingSemantics> {
self.local_binding_semantics.get(&slot)
}
pub fn get_binding_semantics(&self, slot: u16) -> Option<&BindingSemantics> {
self.binding_semantics.get(&slot)
}
pub fn register_function_return_type(&mut self, func_name: &str, return_type: &str) {
self.function_return_types
.insert(func_name.to_string(), return_type.to_string());
}
pub fn get_function_return_type(&self, func_name: &str) -> Option<&String> {
self.function_return_types.get(func_name)
}
pub fn register_object_field_contracts(
&mut self,
schema_id: SchemaId,
fields: HashMap<String, TypeAnnotation>,
) {
self.object_field_contracts.insert(schema_id, fields);
}
pub fn get_object_field_contract(
&self,
schema_id: SchemaId,
field_name: &str,
) -> Option<&TypeAnnotation> {
self.object_field_contracts
.get(&schema_id)
.and_then(|fields| fields.get(field_name))
}
fn resolve_type_info(&self, mut type_info: VariableTypeInfo) -> VariableTypeInfo {
if let Some(ref type_name) = type_info.type_name {
if let Some(schema) = self.schema_registry.get(type_name) {
type_info.schema_id = Some(schema.id);
type_info.is_definite = true;
}
}
type_info
}
pub fn get_typed_field_info(
&self,
type_name: &str,
field_name: &str,
) -> Option<(SchemaId, usize, u16)> {
let schema = self.schema_registry.get(type_name)?;
let field = schema.get_field(field_name)?;
Some((schema.id, field.offset, field.index))
}
pub fn get_row_view_column_id(
&self,
slot: u16,
is_local: bool,
field_name: &str,
) -> Option<u32> {
let type_info = if is_local {
self.get_local_type(slot)?
} else {
self.get_binding_type(slot)?
};
if !type_info.is_row_view() {
return None;
}
let type_name = type_info.type_name.as_ref()?;
let schema = self.schema_registry.get(type_name)?;
let field = schema.get_field(field_name)?;
Some(field.index as u32)
}
pub fn can_use_typed_access(&self, slot: u16, is_local: bool, field_name: &str) -> bool {
let type_info = if is_local {
self.get_local_type(slot)
} else {
self.get_binding_type(slot)
};
if let Some(info) = type_info {
if let Some(ref type_name) = info.type_name {
return self
.schema_registry
.field_offset(type_name, field_name)
.is_some();
}
}
false
}
pub fn get_local_storage_hint(&self, slot: u16) -> Option<StorageHint> {
self.get_local_type(slot).and_then(|info| info.storage_hint)
}
pub fn get_module_binding_storage_hint(&self, slot: u16) -> Option<StorageHint> {
self.get_binding_type(slot)
.and_then(|info| info.storage_hint)
}
pub fn local_uses_nan_sentinel(&self, slot: u16) -> bool {
self.get_local_storage_hint(slot) == Some(StorageHint::NullableFloat64)
}
pub fn module_binding_uses_nan_sentinel(&self, slot: u16) -> bool {
self.get_module_binding_storage_hint(slot) == Some(StorageHint::NullableFloat64)
}
pub fn clear_locals(&mut self) {
self.local_types.clear();
self.local_binding_semantics.clear();
self.local_type_scopes.clear();
self.local_type_scopes.push(HashMap::new());
self.local_binding_semantic_scopes.clear();
self.local_binding_semantic_scopes.push(HashMap::new());
}
pub fn snapshot_local_binding_semantics(&self) -> HashMap<u16, BindingSemantics> {
self.local_binding_semantics.clone()
}
pub fn restore_local_binding_semantics(&mut self, snapshot: HashMap<u16, BindingSemantics>) {
self.local_binding_semantics = snapshot;
}
pub fn snapshot_local_types(&self) -> LocalTypesSnapshot {
LocalTypesSnapshot {
local_types: self.local_types.clone(),
local_type_scopes: self.local_type_scopes.clone(),
}
}
pub fn restore_local_types(&mut self, snapshot: LocalTypesSnapshot) {
self.local_types = snapshot.local_types;
self.local_type_scopes = snapshot.local_type_scopes;
}
#[deprecated(
since = "0.3.0",
note = "Prefer `register_inline_object_schema_typed` per audit \
§4.D.5 W17.2-C (PROPAGATE per-field types at call site). \
The untyped variant routes through the typed variant \
with FieldType::Any per field; the post_inference_verify \
pass absorbs via the __inline_obj_* transitional row."
)]
pub fn register_inline_object_schema(&mut self, field_names: &[&str]) -> SchemaId {
let typed_fields: Vec<(&str, FieldType)> = field_names
.iter()
.map(|name| (*name, FieldType::Any))
.collect();
self.register_inline_object_schema_typed(&typed_fields)
}
pub fn register_inline_object_schema_typed(
&mut self,
fields: &[(&str, FieldType)],
) -> SchemaId {
if let Some(existing) = self.schema_registry.type_names().find_map(|name| {
self.schema_registry.get(name).and_then(|schema| {
if schema.fields.len() != fields.len() {
return None;
}
let same = schema
.fields
.iter()
.zip(fields.iter())
.all(|(f, (n, t))| f.name == *n && f.field_type == *t);
if same { Some(schema.id) } else { None }
})
}) {
return existing;
}
let id = self.inline_object_counter;
self.inline_object_counter += 1;
let type_name = format!("__inline_obj_{}", id);
let field_defs: Vec<(String, FieldType)> = fields
.iter()
.map(|(name, ft)| (name.to_string(), ft.clone()))
.collect();
let schema = TypeSchema::new(&type_name, field_defs);
let schema_id = schema.id;
self.schema_registry.register(schema);
schema_id
}
pub fn register_named_object_schema(
&mut self,
type_name: &str,
fields: &[(&str, FieldType)],
) -> SchemaId {
let field_defs: Vec<(String, FieldType)> = fields
.iter()
.map(|(name, ft)| (name.to_string(), ft.clone()))
.collect();
let schema = TypeSchema::new(type_name, field_defs);
let schema_id = schema.id;
self.schema_registry.register(schema);
schema_id
}
pub fn register_typed_object_schema(
&mut self,
field_defs: Vec<(String, FieldType)>,
) -> SchemaId {
let id = self.inline_object_counter;
self.inline_object_counter += 1;
let type_name = format!("__inline_obj_{}", id);
let schema = TypeSchema::new(&type_name, field_defs);
let schema_id = schema.id;
self.schema_registry.register(schema);
schema_id
}
pub fn register_v2_layout(&mut self, schema_id: SchemaId, layout: StructLayout) {
self.v2_layouts.insert(schema_id, layout);
}
pub fn get_v2_layout(&self, schema_id: SchemaId) -> Option<&StructLayout> {
self.v2_layouts.get(&schema_id)
}
pub fn is_typed_array(&self, slot: u16) -> Option<FieldKind> {
self.local_types.get(&slot)?.v2_array_element_kind
}
pub fn is_typed_struct(&self, slot: u16) -> Option<SchemaId> {
self.local_types.get(&slot)?.v2_struct_layout
}
}
impl Default for TypeTracker {
fn default() -> Self {
Self::empty()
}
}
#[derive(Debug)]
pub struct ProofGap {
site: &'static str,
detail: String,
_seal: ProofGapSeal,
}
#[derive(Debug)]
struct ProofGapSeal(());
impl ProofGap {
pub fn site(&self) -> &'static str {
self.site
}
pub fn detail(&self) -> &str {
&self.detail
}
}
impl std::fmt::Display for ProofGap {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"E_TYPED_OPCODE_WITHOUT_PROOF at {}: {}",
self.site, self.detail
)
}
}
impl std::error::Error for ProofGap {}
#[inline]
pub fn prove_native_kind(
site: &'static str,
claimed_kind: NativeKind,
) -> Result<NativeKind, ProofGap> {
Ok(claimed_kind)
}
#[allow(dead_code)]
fn proof_gap(site: &'static str, detail: impl Into<String>) -> ProofGap {
ProofGap {
site,
detail: detail.into(),
_seal: ProofGapSeal(()),
}
}
pub fn proof_gap_unresolved_operand(site: &'static str, detail: impl Into<String>) -> ProofGap {
proof_gap(site, detail)
}
#[cfg(test)]
mod tests {
use super::*;
use shape_runtime::type_schema::TypeSchemaBuilder;
#[test]
fn test_basic_type_tracking() {
let mut registry = TypeSchemaRegistry::new();
TypeSchemaBuilder::new("Point")
.f64_field("x")
.f64_field("y")
.register(&mut registry);
let mut tracker = TypeTracker::new(registry);
tracker.set_local_type(0, VariableTypeInfo::named("Point".to_string()));
assert!(tracker.can_use_typed_access(0, true, "x"));
assert!(tracker.can_use_typed_access(0, true, "y"));
assert!(!tracker.can_use_typed_access(0, true, "z")); }
#[test]
fn test_scope_tracking() {
let mut tracker = TypeTracker::empty();
tracker.set_local_type(0, VariableTypeInfo::named("Outer".to_string()));
tracker.push_scope();
tracker.set_local_type(1, VariableTypeInfo::named("Inner".to_string()));
assert!(tracker.get_local_type(0).is_some());
assert!(tracker.get_local_type(1).is_some());
tracker.pop_scope();
assert!(tracker.get_local_type(0).is_some());
assert!(tracker.get_local_type(1).is_none());
}
#[test]
fn test_binding_semantics_scope_tracking() {
let mut tracker = TypeTracker::empty();
tracker.set_local_binding_semantics(
0,
BindingSemantics::deferred(BindingOwnershipClass::OwnedImmutable),
);
tracker.set_binding_semantics(
5,
BindingSemantics::deferred(BindingOwnershipClass::Flexible),
);
tracker.push_scope();
tracker.set_local_binding_semantics(
1,
BindingSemantics::deferred(BindingOwnershipClass::OwnedMutable),
);
assert_eq!(
tracker
.get_local_binding_semantics(0)
.map(|s| s.ownership_class),
Some(BindingOwnershipClass::OwnedImmutable)
);
assert_eq!(
tracker
.get_local_binding_semantics(1)
.map(|s| s.ownership_class),
Some(BindingOwnershipClass::OwnedMutable)
);
assert_eq!(
tracker.get_binding_semantics(5).map(|s| s.ownership_class),
Some(BindingOwnershipClass::Flexible)
);
tracker.pop_scope();
assert!(tracker.get_local_binding_semantics(1).is_none());
assert!(tracker.get_local_binding_semantics(0).is_some());
assert!(tracker.get_binding_semantics(5).is_some());
}
#[test]
fn test_binding_storage_class_updates() {
let mut tracker = TypeTracker::empty();
tracker.set_local_binding_semantics(
0,
BindingSemantics::deferred(BindingOwnershipClass::OwnedMutable),
);
tracker.set_binding_semantics(
4,
BindingSemantics::deferred(BindingOwnershipClass::Flexible),
);
tracker.set_local_binding_storage_class(0, BindingStorageClass::Reference);
tracker.set_binding_storage_class(4, BindingStorageClass::SharedCow);
assert_eq!(
tracker
.get_local_binding_semantics(0)
.map(|s| s.storage_class),
Some(BindingStorageClass::Reference)
);
assert_eq!(
tracker.get_binding_semantics(4).map(|s| s.storage_class),
Some(BindingStorageClass::SharedCow)
);
tracker.clear_locals();
assert!(tracker.get_local_binding_semantics(0).is_none());
assert!(tracker.get_binding_semantics(4).is_some());
}
#[test]
fn test_function_return_types() {
let mut tracker = TypeTracker::empty();
tracker.register_function_return_type("get_point", "Point");
assert_eq!(
tracker.get_function_return_type("get_point"),
Some(&"Point".to_string())
);
assert!(tracker.get_function_return_type("unknown").is_none());
}
#[test]
fn test_typed_field_info() {
let mut registry = TypeSchemaRegistry::new();
TypeSchemaBuilder::new("Vector3")
.f64_field("x")
.f64_field("y")
.f64_field("z")
.register(&mut registry);
let tracker = TypeTracker::new(registry);
let info = tracker.get_typed_field_info("Vector3", "y");
assert!(info.is_some());
let (schema_id, offset, index) = info.unwrap();
assert!(schema_id > 0);
assert_eq!(offset, 8); assert_eq!(index, 1);
}
#[test]
fn test_unknown_type() {
let tracker = TypeTracker::empty();
assert!(!tracker.can_use_typed_access(0, true, "field"));
}
#[test]
fn test_binding_type_tracking() {
let mut registry = TypeSchemaRegistry::new();
TypeSchemaBuilder::new("Config")
.f64_field("threshold")
.string_field("name")
.register(&mut registry);
let mut tracker = TypeTracker::new(registry);
tracker.set_binding_type(5, VariableTypeInfo::named("Config".to_string()));
assert!(tracker.can_use_typed_access(5, false, "threshold"));
assert!(tracker.can_use_typed_access(5, false, "name"));
assert!(!tracker.can_use_typed_access(5, false, "unknown"));
}
#[test]
fn test_storage_hint_inference() {
assert_eq!(
VariableTypeInfo::infer_storage_hint("Number"),
Some(StorageHint::Float64)
);
assert_eq!(
VariableTypeInfo::infer_storage_hint("Integer"),
Some(StorageHint::Int64)
);
assert_eq!(
VariableTypeInfo::infer_storage_hint("Bool"),
Some(StorageHint::Bool)
);
assert_eq!(
VariableTypeInfo::infer_storage_hint("String"),
Some(StorageHint::String)
);
assert_eq!(
VariableTypeInfo::infer_storage_hint("Option<Number>"),
Some(StorageHint::NullableFloat64)
);
assert_eq!(
VariableTypeInfo::infer_storage_hint("Option<Integer>"),
Some(StorageHint::NullableInt64)
);
assert_eq!(
VariableTypeInfo::infer_storage_hint("Option<byte>"),
Some(StorageHint::NullableUInt8)
);
assert_eq!(
VariableTypeInfo::infer_storage_hint("Option<char>"),
Some(StorageHint::NullableInt8)
);
assert_eq!(
VariableTypeInfo::infer_storage_hint("Option<u32>"),
Some(StorageHint::NullableUInt32)
);
assert_eq!(
VariableTypeInfo::infer_storage_hint("SomeCustomType"),
None
);
}
#[test]
fn test_width_integer_storage_hint_inference() {
assert_eq!(
VariableTypeInfo::infer_storage_hint("i8"),
Some(StorageHint::Int8)
);
assert_eq!(
VariableTypeInfo::infer_storage_hint("byte"),
Some(StorageHint::UInt8)
);
assert_eq!(
VariableTypeInfo::infer_storage_hint("char"),
Some(StorageHint::Int8)
);
assert_eq!(
VariableTypeInfo::infer_storage_hint("u16"),
Some(StorageHint::UInt16)
);
assert_eq!(
VariableTypeInfo::infer_storage_hint("i32"),
Some(StorageHint::Int32)
);
assert_eq!(
VariableTypeInfo::infer_storage_hint("u64"),
Some(StorageHint::UInt64)
);
assert_eq!(
VariableTypeInfo::infer_storage_hint("isize"),
Some(StorageHint::IntSize)
);
assert_eq!(
VariableTypeInfo::infer_storage_hint("usize"),
Some(StorageHint::UIntSize)
);
}
#[test]
fn test_concrete_numeric_type_inference() {
assert_eq!(
VariableTypeInfo::infer_numeric_runtime_name("int"),
Some("i64".to_string())
);
assert_eq!(
VariableTypeInfo::infer_numeric_runtime_name("i16"),
Some("i16".to_string())
);
assert_eq!(
VariableTypeInfo::infer_numeric_runtime_name("byte"),
Some("u8".to_string())
);
assert_eq!(
VariableTypeInfo::infer_numeric_runtime_name("Option<f32>"),
Some("f32".to_string())
);
assert_eq!(
VariableTypeInfo::infer_numeric_runtime_name("SomeCustomType"),
None
);
}
#[test]
fn test_native_kind_from_storage_type() {
assert_eq!(
native_kind_from_storage_type(&StorageType::Float64),
Some(NativeKind::Float64)
);
assert_eq!(
native_kind_from_storage_type(&StorageType::NullableFloat64),
Some(NativeKind::NullableFloat64)
);
assert_eq!(
native_kind_from_storage_type(&StorageType::Dynamic),
None
);
}
#[test]
fn test_nullable_number_type() {
let info = VariableTypeInfo::nullable_number();
assert!(info.uses_nan_sentinel());
assert_eq!(info.storage_hint, Some(StorageHint::NullableFloat64));
}
#[test]
fn test_row_view_column_id_resolution() {
let mut registry = TypeSchemaRegistry::new();
TypeSchemaBuilder::new("Candle")
.f64_field("open")
.f64_field("high")
.f64_field("low")
.f64_field("close")
.i64_field("volume")
.register(&mut registry);
let mut tracker = TypeTracker::new(registry);
let schema = tracker.schema_registry().get("Candle").unwrap();
let schema_id = schema.id;
tracker.set_local_type(
0,
VariableTypeInfo::row_view(schema_id, "Candle".to_string()),
);
assert_eq!(tracker.get_row_view_column_id(0, true, "open"), Some(0));
assert_eq!(tracker.get_row_view_column_id(0, true, "high"), Some(1));
assert_eq!(tracker.get_row_view_column_id(0, true, "close"), Some(3));
assert_eq!(tracker.get_row_view_column_id(0, true, "volume"), Some(4));
assert_eq!(tracker.get_row_view_column_id(0, true, "nonexistent"), None);
tracker.set_local_type(1, VariableTypeInfo::named("Candle".to_string()));
assert_eq!(tracker.get_row_view_column_id(1, true, "open"), None);
}
#[test]
fn test_tracker_storage_hints() {
let mut tracker = TypeTracker::empty();
tracker.set_local_type(0, VariableTypeInfo::nullable_number());
assert!(tracker.local_uses_nan_sentinel(0));
tracker.set_local_type(1, VariableTypeInfo::number());
assert!(!tracker.local_uses_nan_sentinel(1));
assert!(!tracker.local_uses_nan_sentinel(99));
}
#[test]
fn test_datatable_type_info() {
let mut registry = TypeSchemaRegistry::new();
TypeSchemaBuilder::new("Trade")
.f64_field("price")
.i64_field("volume")
.string_field("symbol")
.register(&mut registry);
let mut tracker = TypeTracker::new(registry);
let schema = tracker.schema_registry().get("Trade").unwrap();
let schema_id = schema.id;
tracker.set_local_type(
0,
VariableTypeInfo::datatable(schema_id, "Trade".to_string()),
);
let info = tracker.get_local_type(0).unwrap();
assert!(info.is_datatable());
assert!(!info.is_row_view());
assert_eq!(info.schema_id, Some(schema_id));
assert_eq!(info.type_name.as_deref(), Some("Trade"));
tracker.set_local_type(
1,
VariableTypeInfo::row_view(schema_id, "Trade".to_string()),
);
let info = tracker.get_local_type(1).unwrap();
assert!(!info.is_datatable());
assert!(info.is_row_view());
}
#[test]
fn test_v2_struct_layout_registration() {
use shape_value::v2::struct_layout::{FieldKind, StructLayout};
let mut tracker = TypeTracker::empty();
let layout = StructLayout::new(&[("x", FieldKind::F64), ("y", FieldKind::F64)]);
assert_eq!(layout.total_size(), 24);
let schema_id: SchemaId = 42;
tracker.register_v2_layout(schema_id, layout);
let retrieved = tracker.get_v2_layout(schema_id);
assert!(retrieved.is_some());
let retrieved = retrieved.unwrap();
assert_eq!(retrieved.field_count(), 2);
assert_eq!(retrieved.field_offset(0), 8);
assert_eq!(retrieved.field_offset(1), 16);
assert_eq!(retrieved.total_size(), 24);
assert!(tracker.get_v2_layout(999).is_none());
}
#[test]
fn test_v2_typed_array_element_kind() {
use shape_value::v2::struct_layout::FieldKind;
let mut tracker = TypeTracker::empty();
let mut info = VariableTypeInfo::named("Array<number>".to_string());
info.v2_array_element_kind = Some(FieldKind::F64);
tracker.set_local_type(0, info);
assert_eq!(tracker.is_typed_array(0), Some(FieldKind::F64));
assert_eq!(tracker.is_typed_array(1), None);
let mut info2 = VariableTypeInfo::named("Array<i32>".to_string());
info2.v2_array_element_kind = Some(FieldKind::I32);
tracker.set_local_type(1, info2);
assert_eq!(tracker.is_typed_array(1), Some(FieldKind::I32));
}
#[test]
fn test_v2_typed_struct_on_variable() {
use shape_value::v2::struct_layout::{FieldKind, StructLayout};
let mut tracker = TypeTracker::empty();
let layout = StructLayout::new(&[
("name", FieldKind::Ptr),
("age", FieldKind::I32),
("score", FieldKind::F64),
]);
let schema_id: SchemaId = 100;
tracker.register_v2_layout(schema_id, layout);
let mut info = VariableTypeInfo::named("Person".to_string());
info.v2_struct_layout = Some(schema_id);
tracker.set_local_type(0, info);
assert_eq!(tracker.is_typed_struct(0), Some(schema_id));
assert_eq!(tracker.is_typed_struct(1), None);
let layout = tracker.get_v2_layout(schema_id).unwrap();
assert_eq!(layout.field_count(), 3);
assert_eq!(layout.field_kind(0), FieldKind::Ptr);
assert_eq!(layout.field_kind(1), FieldKind::I32);
assert_eq!(layout.field_kind(2), FieldKind::F64);
assert_eq!(layout.heap_field_mask, 0b001); }
#[test]
fn test_v2_fields_default_none() {
let info = VariableTypeInfo::unknown();
assert!(info.v2_array_element_kind.is_none());
assert!(info.v2_struct_layout.is_none());
let info = VariableTypeInfo::number();
assert!(info.v2_array_element_kind.is_none());
assert!(info.v2_struct_layout.is_none());
let info = VariableTypeInfo::named("Foo".to_string());
assert!(info.v2_array_element_kind.is_none());
assert!(info.v2_struct_layout.is_none());
let info = VariableTypeInfo::known(1, "Bar".to_string());
assert!(info.v2_array_element_kind.is_none());
assert!(info.v2_struct_layout.is_none());
}
#[test]
fn test_proof_gap_unresolved_operand_surfaces_cleanly() {
let gap = proof_gap_unresolved_operand(
"emit_typed_arithmetic",
"operand `x` of `Mul` has an unresolved type",
);
assert_eq!(gap.site(), "emit_typed_arithmetic");
assert!(gap.detail().contains("unresolved type"));
let rendered = gap.to_string();
assert!(
rendered.starts_with("E_TYPED_OPCODE_WITHOUT_PROOF at emit_typed_arithmetic:"),
"diagnostic must be the labelled proof-gap form, got: {rendered}"
);
assert!(rendered.contains("operand `x` of `Mul`"));
}
}