1use std::collections::HashMap;
39
40use serde::{Deserialize, Serialize};
41use shape_ast::ast::TypeAnnotation;
42use shape_runtime::type_schema::{FieldType, SchemaId, TypeSchema, TypeSchemaRegistry};
43use shape_runtime::type_system::{BuiltinTypes, StorageType};
44use shape_value::v2::struct_layout::{FieldKind, StructLayout};
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum NumericType {
53 Int,
55 IntWidth(shape_ast::IntWidth),
57 Number,
59 Decimal,
61}
62
63
64pub use shape_value::NativeKind;
69
70pub type StorageHint = NativeKind;
72
73pub fn native_kind_from_storage_type(st: &StorageType) -> Option<NativeKind> {
90 match st {
91 StorageType::Float64 => Some(NativeKind::Float64),
92 StorageType::Int64 => Some(NativeKind::Int64),
93 StorageType::Bool => Some(NativeKind::Bool),
94 StorageType::String => Some(NativeKind::String),
95
96 StorageType::NullableFloat64 => Some(NativeKind::NullableFloat64),
97 StorageType::NullableInt64 => Some(NativeKind::NullableInt64),
98 StorageType::NullableBool => Some(NativeKind::Bool), StorageType::Array(_)
101 | StorageType::Table { .. }
102 | StorageType::Object
103 | StorageType::Result { .. }
104 | StorageType::TaggedUnion { .. }
105 | StorageType::Function
106 | StorageType::Struct(_)
107 | StorageType::Dynamic => None,
108 }
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
140pub struct FrameDescriptor {
141 pub slots: Vec<NativeKind>,
146
147 #[serde(default)]
153 pub return_kind: Option<NativeKind>,
154}
155
156impl FrameDescriptor {
157 pub fn new() -> Self {
161 Self {
162 slots: Vec::new(),
163 return_kind: None,
164 }
165 }
166
167 pub fn from_slots(slots: Vec<NativeKind>) -> Self {
170 Self {
171 slots,
172 return_kind: None,
173 }
174 }
175
176 #[inline]
178 pub fn len(&self) -> usize {
179 self.slots.len()
180 }
181
182 #[inline]
184 pub fn is_empty(&self) -> bool {
185 self.slots.is_empty()
186 }
187
188 #[inline]
192 pub fn slot(&self, index: usize) -> Option<NativeKind> {
193 self.slots.get(index).copied()
194 }
195}
196
197impl Default for FrameDescriptor {
198 fn default() -> Self {
199 Self::new()
200 }
201}
202
203#[derive(Debug, Clone, PartialEq)]
208pub enum VariableKind {
209 Value,
211 Table { element_type: String },
214 RowView { element_type: String },
217 Column {
219 element_type: String,
220 column_type: String,
221 },
222 Indexed {
225 element_type: String,
226 index_column: String,
227 },
228}
229
230#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
236pub enum BindingOwnershipClass {
237 OwnedImmutable,
239 OwnedMutable,
241 Flexible,
243}
244
245#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
246pub enum Aliasability {
247 Unique,
249 SharedImmutable,
251 SharedMutable,
253}
254
255#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
256pub enum MutationCapability {
257 Immutable,
259 LocalMutable,
261 SharedMutable,
263}
264
265#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
266pub enum EscapeStatus {
267 Local,
269 Captured,
271 Escaped,
273}
274
275#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
290pub enum BindingStorageClass {
291 Deferred,
292 Direct,
293 UniqueHeap,
294 SharedCow,
295 Reference,
296 LocalMutablePtr,
299}
300
301#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
303pub struct BindingSemantics {
304 pub ownership_class: BindingOwnershipClass,
305 pub storage_class: BindingStorageClass,
306 pub aliasability: Aliasability,
307 pub mutation_capability: MutationCapability,
308 pub escape_status: EscapeStatus,
309 pub return_ownership_hint: Option<crate::mir::ReturnOwnershipMode>,
314}
315
316impl BindingSemantics {
317 pub const fn deferred(ownership_class: BindingOwnershipClass) -> Self {
318 Self {
319 ownership_class,
320 storage_class: BindingStorageClass::Deferred,
321 aliasability: Aliasability::Unique,
322 mutation_capability: match ownership_class {
323 BindingOwnershipClass::OwnedImmutable => MutationCapability::Immutable,
324 BindingOwnershipClass::OwnedMutable => MutationCapability::LocalMutable,
325 BindingOwnershipClass::Flexible => MutationCapability::SharedMutable,
326 },
327 escape_status: EscapeStatus::Local,
328 return_ownership_hint: None,
329 }
330 }
331}
332
333#[derive(Debug, Clone)]
335pub struct VariableTypeInfo {
336 pub schema_id: Option<SchemaId>,
338 pub type_name: Option<String>,
340 pub is_definite: bool,
342 pub storage_hint: Option<StorageHint>,
352 pub concrete_numeric_type: Option<String>,
355 pub kind: VariableKind,
357 pub v2_array_element_kind: Option<FieldKind>,
359 pub v2_struct_layout: Option<SchemaId>,
361}
362
363impl VariableTypeInfo {
364 pub fn known(schema_id: SchemaId, type_name: String) -> Self {
366 let concrete_numeric_type = Self::infer_numeric_runtime_name(&type_name);
367 Self {
368 schema_id: Some(schema_id),
369 type_name: Some(type_name),
370 is_definite: true,
371 storage_hint: None,
372 concrete_numeric_type,
373 kind: VariableKind::Value,
374 v2_array_element_kind: None,
375 v2_struct_layout: None,
376 }
377 }
378
379 pub fn unknown() -> Self {
381 Self {
382 schema_id: None,
383 type_name: None,
384 is_definite: false,
385 storage_hint: None,
386 concrete_numeric_type: None,
387 kind: VariableKind::Value,
388 v2_array_element_kind: None,
389 v2_struct_layout: None,
390 }
391 }
392
393 pub fn named(type_name: String) -> Self {
395 let storage_hint = Self::infer_storage_hint(&type_name);
397 let concrete_numeric_type = Self::infer_numeric_runtime_name(&type_name);
398 Self {
399 schema_id: None,
400 type_name: Some(type_name),
401 is_definite: false,
402 storage_hint,
403 concrete_numeric_type,
404 kind: VariableKind::Value,
405 v2_array_element_kind: None,
406 v2_struct_layout: None,
407 }
408 }
409
410 pub fn with_storage(type_name: String, storage_hint: StorageHint) -> Self {
412 let concrete_numeric_type = Self::infer_numeric_runtime_name(&type_name);
413 Self {
414 schema_id: None,
415 type_name: Some(type_name),
416 is_definite: true,
417 storage_hint: Some(storage_hint),
418 concrete_numeric_type,
419 kind: VariableKind::Value,
420 v2_array_element_kind: None,
421 v2_struct_layout: None,
422 }
423 }
424
425 pub fn nullable_number() -> Self {
427 Self {
428 schema_id: None,
429 type_name: Some("Option<Number>".to_string()),
430 is_definite: true,
431 storage_hint: Some(StorageHint::NullableFloat64),
432 concrete_numeric_type: Some("f64".to_string()),
433 kind: VariableKind::Value,
434 v2_array_element_kind: None,
435 v2_struct_layout: None,
436 }
437 }
438
439 pub fn number() -> Self {
441 Self {
442 schema_id: None,
443 type_name: Some("Number".to_string()),
444 is_definite: true,
445 storage_hint: Some(StorageHint::Float64),
446 concrete_numeric_type: Some("f64".to_string()),
447 kind: VariableKind::Value,
448 v2_array_element_kind: None,
449 v2_struct_layout: None,
450 }
451 }
452
453 pub fn row_view(schema_id: SchemaId, type_name: String) -> Self {
455 Self {
456 schema_id: Some(schema_id),
457 type_name: Some(type_name.clone()),
458 is_definite: true,
459 storage_hint: None,
460 concrete_numeric_type: None,
461 kind: VariableKind::RowView {
462 element_type: type_name,
463 },
464 v2_array_element_kind: None,
465 v2_struct_layout: None,
466 }
467 }
468
469 pub fn datatable(schema_id: SchemaId, type_name: String) -> Self {
471 Self {
472 schema_id: Some(schema_id),
473 type_name: Some(type_name.clone()),
474 is_definite: true,
475 storage_hint: None,
476 concrete_numeric_type: None,
477 kind: VariableKind::Table {
478 element_type: type_name,
479 },
480 v2_array_element_kind: None,
481 v2_struct_layout: None,
482 }
483 }
484
485 pub fn column(schema_id: SchemaId, type_name: String, element_type: String) -> Self {
487 Self {
488 schema_id: Some(schema_id),
489 type_name: Some(type_name.clone()),
490 is_definite: true,
491 storage_hint: None,
492 concrete_numeric_type: None,
493 kind: VariableKind::Column {
494 element_type,
495 column_type: type_name,
496 },
497 v2_array_element_kind: None,
498 v2_struct_layout: None,
499 }
500 }
501
502 pub fn indexed(schema_id: SchemaId, type_name: String, index_column: String) -> Self {
504 Self {
505 schema_id: Some(schema_id),
506 type_name: Some(type_name.clone()),
507 is_definite: true,
508 storage_hint: None,
509 concrete_numeric_type: None,
510 kind: VariableKind::Indexed {
511 element_type: type_name,
512 index_column,
513 },
514 v2_array_element_kind: None,
515 v2_struct_layout: None,
516 }
517 }
518
519 pub fn is_known(&self) -> bool {
521 self.schema_id.is_some()
522 }
523
524 pub fn uses_nan_sentinel(&self) -> bool {
526 self.storage_hint == Some(StorageHint::NullableFloat64)
527 }
528
529 pub fn is_datatable(&self) -> bool {
531 matches!(self.kind, VariableKind::Table { .. })
532 }
533
534 pub fn is_row_view(&self) -> bool {
536 matches!(self.kind, VariableKind::RowView { .. })
537 }
538
539 pub fn is_column(&self) -> bool {
541 matches!(self.kind, VariableKind::Column { .. })
542 }
543
544 pub fn is_indexed(&self) -> bool {
546 matches!(self.kind, VariableKind::Indexed { .. })
547 }
548
549 fn infer_storage_hint(type_name: &str) -> Option<StorageHint> {
555 let trimmed = type_name.trim();
556
557 if let Some(inner) = Self::option_inner_type(trimmed) {
558 let inner = inner.trim();
559 if let Some(runtime) = BuiltinTypes::canonical_numeric_runtime_name(inner)
560 && let Some(hint) = Self::storage_hint_for_runtime_numeric(runtime, true)
561 {
562 return Some(hint);
563 }
564 if BuiltinTypes::is_bool_type_name(inner) {
565 return Some(StorageHint::Bool);
566 }
567 if BuiltinTypes::is_string_type_name(inner) {
568 return Some(StorageHint::String);
569 }
570 return None;
571 }
572
573 if let Some(runtime) = BuiltinTypes::canonical_numeric_runtime_name(trimmed)
574 && let Some(hint) = Self::storage_hint_for_runtime_numeric(runtime, false)
575 {
576 return Some(hint);
577 }
578 if BuiltinTypes::is_bool_type_name(trimmed) {
579 return Some(StorageHint::Bool);
580 }
581 if BuiltinTypes::is_string_type_name(trimmed) {
582 return Some(StorageHint::String);
583 }
584 None
585 }
586
587 fn option_inner_type(type_name: &str) -> Option<&str> {
588 type_name
589 .strip_prefix("Option<")
590 .and_then(|inner| inner.strip_suffix('>'))
591 }
592
593 fn storage_hint_for_runtime_numeric(runtime_name: &str, nullable: bool) -> Option<StorageHint> {
594 let base = match runtime_name {
595 "f32" | "f64" => StorageHint::Float64,
596 "i8" => StorageHint::Int8,
597 "u8" => StorageHint::UInt8,
598 "i16" => StorageHint::Int16,
599 "u16" => StorageHint::UInt16,
600 "i32" => StorageHint::Int32,
601 "u32" => StorageHint::UInt32,
602 "i64" => StorageHint::Int64,
603 "u64" => StorageHint::UInt64,
604 "isize" => StorageHint::IntSize,
605 "usize" => StorageHint::UIntSize,
606 _ => return None,
607 };
608 Some(base.with_nullability(nullable))
609 }
610
611 fn infer_numeric_runtime_name(type_name: &str) -> Option<String> {
612 let inner = if type_name.starts_with("Option<") && type_name.ends_with('>') {
613 &type_name["Option<".len()..type_name.len() - 1]
614 } else {
615 type_name
616 };
617 BuiltinTypes::canonical_numeric_runtime_name(inner).map(ToString::to_string)
618 }
619}
620
621#[derive(Debug, Clone)]
627pub struct LocalTypesSnapshot {
628 pub local_types: HashMap<u16, VariableTypeInfo>,
629 pub local_type_scopes: Vec<HashMap<u16, VariableTypeInfo>>,
630}
631
632impl LocalTypesSnapshot {
633 pub fn len(&self) -> usize {
634 self.local_types.len()
635 }
636
637 pub fn is_empty(&self) -> bool {
638 self.local_types.is_empty()
639 }
640}
641
642#[derive(Debug)]
644pub struct TypeTracker {
645 schema_registry: TypeSchemaRegistry,
647
648 local_types: HashMap<u16, VariableTypeInfo>,
650
651 binding_types: HashMap<u16, VariableTypeInfo>,
653
654 local_binding_semantics: HashMap<u16, BindingSemantics>,
656
657 binding_semantics: HashMap<u16, BindingSemantics>,
659
660 local_type_scopes: Vec<HashMap<u16, VariableTypeInfo>>,
662
663 local_binding_semantic_scopes: Vec<HashMap<u16, BindingSemantics>>,
665
666 function_return_types: HashMap<String, String>,
668 object_field_contracts: HashMap<SchemaId, HashMap<String, TypeAnnotation>>,
672
673 pub v2_layouts: HashMap<SchemaId, StructLayout>,
676
677 inline_object_counter: u64,
682}
683
684impl TypeTracker {
685 pub fn new(schema_registry: TypeSchemaRegistry) -> Self {
687 Self {
688 schema_registry,
689 local_types: HashMap::new(),
690 binding_types: HashMap::new(),
691 local_binding_semantics: HashMap::new(),
692 binding_semantics: HashMap::new(),
693 local_type_scopes: vec![HashMap::new()],
694 local_binding_semantic_scopes: vec![HashMap::new()],
695 function_return_types: HashMap::new(),
696 object_field_contracts: HashMap::new(),
697 v2_layouts: HashMap::new(),
698 inline_object_counter: 0,
699 }
700 }
701
702 pub fn empty() -> Self {
704 Self::new(TypeSchemaRegistry::new())
705 }
706
707 pub fn with_stdlib() -> Self {
709 Self::new(TypeSchemaRegistry::with_stdlib_types())
710 }
711
712 pub fn schema_registry(&self) -> &TypeSchemaRegistry {
714 &self.schema_registry
715 }
716
717 pub fn schema_registry_mut(&mut self) -> &mut TypeSchemaRegistry {
719 &mut self.schema_registry
720 }
721
722 pub fn push_scope(&mut self) {
724 self.local_type_scopes.push(HashMap::new());
725 self.local_binding_semantic_scopes.push(HashMap::new());
726 }
727
728 pub fn pop_scope(&mut self) {
730 if let Some(scope) = self.local_type_scopes.pop() {
731 for slot in scope.keys() {
733 self.local_types.remove(slot);
734 }
735 }
736 if let Some(scope) = self.local_binding_semantic_scopes.pop() {
737 for slot in scope.keys() {
738 self.local_binding_semantics.remove(slot);
739 }
740 }
741 }
742
743 pub fn set_local_type(&mut self, slot: u16, type_info: VariableTypeInfo) {
745 let resolved_info = if type_info.type_name.is_some() && type_info.schema_id.is_none() {
747 self.resolve_type_info(type_info)
748 } else {
749 type_info
750 };
751
752 if let Some(scope) = self.local_type_scopes.last_mut() {
754 scope.insert(slot, resolved_info.clone());
755 }
756 self.local_types.insert(slot, resolved_info);
757 }
758
759 pub fn set_binding_type(&mut self, slot: u16, type_info: VariableTypeInfo) {
761 let resolved_info = if type_info.type_name.is_some() && type_info.schema_id.is_none() {
762 self.resolve_type_info(type_info)
763 } else {
764 type_info
765 };
766 self.binding_types.insert(slot, resolved_info);
767 }
768
769 pub fn set_local_binding_semantics(&mut self, slot: u16, semantics: BindingSemantics) {
771 if let Some(scope) = self.local_binding_semantic_scopes.last_mut() {
772 scope.insert(slot, semantics);
773 }
774 self.local_binding_semantics.insert(slot, semantics);
775 }
776
777 pub fn set_binding_semantics(&mut self, slot: u16, semantics: BindingSemantics) {
779 self.binding_semantics.insert(slot, semantics);
780 }
781
782 pub fn set_local_binding_storage_class(
784 &mut self,
785 slot: u16,
786 storage_class: BindingStorageClass,
787 ) {
788 if let Some(existing) = self.local_binding_semantics.get_mut(&slot) {
789 existing.storage_class = storage_class;
790 }
791 for scope in self.local_binding_semantic_scopes.iter_mut().rev() {
792 if let Some(existing) = scope.get_mut(&slot) {
793 existing.storage_class = storage_class;
794 break;
795 }
796 }
797 }
798
799 pub fn set_binding_storage_class(&mut self, slot: u16, storage_class: BindingStorageClass) {
801 if let Some(existing) = self.binding_semantics.get_mut(&slot) {
802 existing.storage_class = storage_class;
803 }
804 }
805
806 pub fn get_local_type(&self, slot: u16) -> Option<&VariableTypeInfo> {
808 self.local_types.get(&slot)
809 }
810
811 pub fn get_binding_type(&self, slot: u16) -> Option<&VariableTypeInfo> {
813 self.binding_types.get(&slot)
814 }
815
816 pub fn get_local_binding_semantics(&self, slot: u16) -> Option<&BindingSemantics> {
818 self.local_binding_semantics.get(&slot)
819 }
820
821 pub fn get_binding_semantics(&self, slot: u16) -> Option<&BindingSemantics> {
823 self.binding_semantics.get(&slot)
824 }
825
826 pub fn register_function_return_type(&mut self, func_name: &str, return_type: &str) {
828 self.function_return_types
829 .insert(func_name.to_string(), return_type.to_string());
830 }
831
832 pub fn get_function_return_type(&self, func_name: &str) -> Option<&String> {
834 self.function_return_types.get(func_name)
835 }
836
837 pub fn register_object_field_contracts(
839 &mut self,
840 schema_id: SchemaId,
841 fields: HashMap<String, TypeAnnotation>,
842 ) {
843 self.object_field_contracts.insert(schema_id, fields);
844 }
845
846 pub fn get_object_field_contract(
848 &self,
849 schema_id: SchemaId,
850 field_name: &str,
851 ) -> Option<&TypeAnnotation> {
852 self.object_field_contracts
853 .get(&schema_id)
854 .and_then(|fields| fields.get(field_name))
855 }
856
857 fn resolve_type_info(&self, mut type_info: VariableTypeInfo) -> VariableTypeInfo {
859 if let Some(ref type_name) = type_info.type_name {
860 if let Some(schema) = self.schema_registry.get(type_name) {
861 type_info.schema_id = Some(schema.id);
862 type_info.is_definite = true;
863 }
864 }
865 type_info
866 }
867
868 pub fn get_typed_field_info(
872 &self,
873 type_name: &str,
874 field_name: &str,
875 ) -> Option<(SchemaId, usize, u16)> {
876 let schema = self.schema_registry.get(type_name)?;
877 let field = schema.get_field(field_name)?;
878 Some((schema.id, field.offset, field.index))
879 }
880
881 pub fn get_row_view_column_id(
886 &self,
887 slot: u16,
888 is_local: bool,
889 field_name: &str,
890 ) -> Option<u32> {
891 let type_info = if is_local {
892 self.get_local_type(slot)?
893 } else {
894 self.get_binding_type(slot)?
895 };
896 if !type_info.is_row_view() {
897 return None;
898 }
899 let type_name = type_info.type_name.as_ref()?;
900 let schema = self.schema_registry.get(type_name)?;
901 let field = schema.get_field(field_name)?;
902 Some(field.index as u32)
903 }
904
905 pub fn can_use_typed_access(&self, slot: u16, is_local: bool, field_name: &str) -> bool {
907 let type_info = if is_local {
908 self.get_local_type(slot)
909 } else {
910 self.get_binding_type(slot)
911 };
912
913 if let Some(info) = type_info {
914 if let Some(ref type_name) = info.type_name {
915 return self
916 .schema_registry
917 .field_offset(type_name, field_name)
918 .is_some();
919 }
920 }
921 false
922 }
923
924 pub fn get_local_storage_hint(&self, slot: u16) -> Option<StorageHint> {
929 self.get_local_type(slot).and_then(|info| info.storage_hint)
930 }
931
932 pub fn get_module_binding_storage_hint(&self, slot: u16) -> Option<StorageHint> {
936 self.get_binding_type(slot)
937 .and_then(|info| info.storage_hint)
938 }
939
940 pub fn local_uses_nan_sentinel(&self, slot: u16) -> bool {
942 self.get_local_storage_hint(slot) == Some(StorageHint::NullableFloat64)
943 }
944
945 pub fn module_binding_uses_nan_sentinel(&self, slot: u16) -> bool {
947 self.get_module_binding_storage_hint(slot) == Some(StorageHint::NullableFloat64)
948 }
949
950 pub fn clear_locals(&mut self) {
952 self.local_types.clear();
953 self.local_binding_semantics.clear();
954 self.local_type_scopes.clear();
955 self.local_type_scopes.push(HashMap::new());
956 self.local_binding_semantic_scopes.clear();
957 self.local_binding_semantic_scopes.push(HashMap::new());
958 }
959
960 pub fn snapshot_local_binding_semantics(&self) -> HashMap<u16, BindingSemantics> {
968 self.local_binding_semantics.clone()
969 }
970
971 pub fn restore_local_binding_semantics(&mut self, snapshot: HashMap<u16, BindingSemantics>) {
975 self.local_binding_semantics = snapshot;
976 }
977
978 pub fn snapshot_local_types(&self) -> LocalTypesSnapshot {
995 LocalTypesSnapshot {
996 local_types: self.local_types.clone(),
997 local_type_scopes: self.local_type_scopes.clone(),
998 }
999 }
1000
1001 pub fn restore_local_types(&mut self, snapshot: LocalTypesSnapshot) {
1006 self.local_types = snapshot.local_types;
1007 self.local_type_scopes = snapshot.local_type_scopes;
1008 }
1009
1010 #[deprecated(
1041 since = "0.3.0",
1042 note = "Prefer `register_inline_object_schema_typed` per audit \
1043 §4.D.5 W17.2-C (PROPAGATE per-field types at call site). \
1044 The untyped variant routes through the typed variant \
1045 with FieldType::Any per field; the post_inference_verify \
1046 pass absorbs via the __inline_obj_* transitional row."
1047 )]
1048 pub fn register_inline_object_schema(&mut self, field_names: &[&str]) -> SchemaId {
1049 let typed_fields: Vec<(&str, FieldType)> = field_names
1054 .iter()
1055 .map(|name| (*name, FieldType::Any))
1056 .collect();
1057 self.register_inline_object_schema_typed(&typed_fields)
1058 }
1059
1060 pub fn register_inline_object_schema_typed(
1066 &mut self,
1067 fields: &[(&str, FieldType)],
1068 ) -> SchemaId {
1069 if let Some(existing) = self.schema_registry.type_names().find_map(|name| {
1070 self.schema_registry.get(name).and_then(|schema| {
1071 if schema.fields.len() != fields.len() {
1072 return None;
1073 }
1074 let same = schema
1075 .fields
1076 .iter()
1077 .zip(fields.iter())
1078 .all(|(f, (n, t))| f.name == *n && f.field_type == *t);
1079 if same { Some(schema.id) } else { None }
1080 })
1081 }) {
1082 return existing;
1083 }
1084
1085 let id = self.inline_object_counter;
1086 self.inline_object_counter += 1;
1087 let type_name = format!("__inline_obj_{}", id);
1088 let field_defs: Vec<(String, FieldType)> = fields
1089 .iter()
1090 .map(|(name, ft)| (name.to_string(), ft.clone()))
1091 .collect();
1092 let schema = TypeSchema::new(&type_name, field_defs);
1093 let schema_id = schema.id;
1094 self.schema_registry.register(schema);
1095 schema_id
1096 }
1097
1098 pub fn register_named_object_schema(
1103 &mut self,
1104 type_name: &str,
1105 fields: &[(&str, FieldType)],
1106 ) -> SchemaId {
1107 let field_defs: Vec<(String, FieldType)> = fields
1108 .iter()
1109 .map(|(name, ft)| (name.to_string(), ft.clone()))
1110 .collect();
1111
1112 let schema = TypeSchema::new(type_name, field_defs);
1113 let schema_id = schema.id;
1114 self.schema_registry.register(schema);
1115
1116 schema_id
1117 }
1118
1119 pub fn register_typed_object_schema(
1124 &mut self,
1125 field_defs: Vec<(String, FieldType)>,
1126 ) -> SchemaId {
1127 let id = self.inline_object_counter;
1128 self.inline_object_counter += 1;
1129 let type_name = format!("__inline_obj_{}", id);
1130
1131 let schema = TypeSchema::new(&type_name, field_defs);
1132 let schema_id = schema.id;
1133 self.schema_registry.register(schema);
1134
1135 schema_id
1136 }
1137
1138 pub fn register_v2_layout(&mut self, schema_id: SchemaId, layout: StructLayout) {
1142 self.v2_layouts.insert(schema_id, layout);
1143 }
1144
1145 pub fn get_v2_layout(&self, schema_id: SchemaId) -> Option<&StructLayout> {
1147 self.v2_layouts.get(&schema_id)
1148 }
1149
1150 pub fn is_typed_array(&self, slot: u16) -> Option<FieldKind> {
1152 self.local_types.get(&slot)?.v2_array_element_kind
1153 }
1154
1155 pub fn is_typed_struct(&self, slot: u16) -> Option<SchemaId> {
1157 self.local_types.get(&slot)?.v2_struct_layout
1158 }
1159}
1160
1161impl Default for TypeTracker {
1162 fn default() -> Self {
1163 Self::empty()
1164 }
1165}
1166
1167#[derive(Debug)]
1187pub struct ProofGap {
1188 site: &'static str,
1189 detail: String,
1190 _seal: ProofGapSeal,
1191}
1192
1193#[derive(Debug)]
1196struct ProofGapSeal(());
1197
1198impl ProofGap {
1199 pub fn site(&self) -> &'static str {
1201 self.site
1202 }
1203
1204 pub fn detail(&self) -> &str {
1206 &self.detail
1207 }
1208}
1209
1210impl std::fmt::Display for ProofGap {
1211 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1212 write!(
1213 f,
1214 "E_TYPED_OPCODE_WITHOUT_PROOF at {}: {}",
1215 self.site, self.detail
1216 )
1217 }
1218}
1219
1220impl std::error::Error for ProofGap {}
1221
1222#[inline]
1238pub fn prove_native_kind(
1239 site: &'static str,
1240 claimed_kind: NativeKind,
1241) -> Result<NativeKind, ProofGap> {
1242 Ok(claimed_kind)
1244}
1245
1246#[allow(dead_code)]
1249fn proof_gap(site: &'static str, detail: impl Into<String>) -> ProofGap {
1250 ProofGap {
1251 site,
1252 detail: detail.into(),
1253 _seal: ProofGapSeal(()),
1254 }
1255}
1256
1257pub fn proof_gap_unresolved_operand(site: &'static str, detail: impl Into<String>) -> ProofGap {
1269 proof_gap(site, detail)
1270}
1271
1272#[cfg(test)]
1273mod tests {
1274 use super::*;
1275 use shape_runtime::type_schema::TypeSchemaBuilder;
1276
1277 #[test]
1278 fn test_basic_type_tracking() {
1279 let mut registry = TypeSchemaRegistry::new();
1280
1281 TypeSchemaBuilder::new("Point")
1282 .f64_field("x")
1283 .f64_field("y")
1284 .register(&mut registry);
1285
1286 let mut tracker = TypeTracker::new(registry);
1287
1288 tracker.set_local_type(0, VariableTypeInfo::named("Point".to_string()));
1290
1291 assert!(tracker.can_use_typed_access(0, true, "x"));
1293 assert!(tracker.can_use_typed_access(0, true, "y"));
1294 assert!(!tracker.can_use_typed_access(0, true, "z")); }
1296
1297 #[test]
1298 fn test_scope_tracking() {
1299 let mut tracker = TypeTracker::empty();
1300
1301 tracker.set_local_type(0, VariableTypeInfo::named("Outer".to_string()));
1303
1304 tracker.push_scope();
1306 tracker.set_local_type(1, VariableTypeInfo::named("Inner".to_string()));
1307
1308 assert!(tracker.get_local_type(0).is_some());
1309 assert!(tracker.get_local_type(1).is_some());
1310
1311 tracker.pop_scope();
1313
1314 assert!(tracker.get_local_type(0).is_some());
1316 assert!(tracker.get_local_type(1).is_none());
1317 }
1318
1319 #[test]
1320 fn test_binding_semantics_scope_tracking() {
1321 let mut tracker = TypeTracker::empty();
1322
1323 tracker.set_local_binding_semantics(
1324 0,
1325 BindingSemantics::deferred(BindingOwnershipClass::OwnedImmutable),
1326 );
1327 tracker.set_binding_semantics(
1328 5,
1329 BindingSemantics::deferred(BindingOwnershipClass::Flexible),
1330 );
1331
1332 tracker.push_scope();
1333 tracker.set_local_binding_semantics(
1334 1,
1335 BindingSemantics::deferred(BindingOwnershipClass::OwnedMutable),
1336 );
1337
1338 assert_eq!(
1339 tracker
1340 .get_local_binding_semantics(0)
1341 .map(|s| s.ownership_class),
1342 Some(BindingOwnershipClass::OwnedImmutable)
1343 );
1344 assert_eq!(
1345 tracker
1346 .get_local_binding_semantics(1)
1347 .map(|s| s.ownership_class),
1348 Some(BindingOwnershipClass::OwnedMutable)
1349 );
1350 assert_eq!(
1351 tracker.get_binding_semantics(5).map(|s| s.ownership_class),
1352 Some(BindingOwnershipClass::Flexible)
1353 );
1354
1355 tracker.pop_scope();
1356
1357 assert!(tracker.get_local_binding_semantics(1).is_none());
1358 assert!(tracker.get_local_binding_semantics(0).is_some());
1359 assert!(tracker.get_binding_semantics(5).is_some());
1360 }
1361
1362 #[test]
1363 fn test_binding_storage_class_updates() {
1364 let mut tracker = TypeTracker::empty();
1365 tracker.set_local_binding_semantics(
1366 0,
1367 BindingSemantics::deferred(BindingOwnershipClass::OwnedMutable),
1368 );
1369 tracker.set_binding_semantics(
1370 4,
1371 BindingSemantics::deferred(BindingOwnershipClass::Flexible),
1372 );
1373
1374 tracker.set_local_binding_storage_class(0, BindingStorageClass::Reference);
1375 tracker.set_binding_storage_class(4, BindingStorageClass::SharedCow);
1376
1377 assert_eq!(
1378 tracker
1379 .get_local_binding_semantics(0)
1380 .map(|s| s.storage_class),
1381 Some(BindingStorageClass::Reference)
1382 );
1383 assert_eq!(
1384 tracker.get_binding_semantics(4).map(|s| s.storage_class),
1385 Some(BindingStorageClass::SharedCow)
1386 );
1387
1388 tracker.clear_locals();
1389 assert!(tracker.get_local_binding_semantics(0).is_none());
1390 assert!(tracker.get_binding_semantics(4).is_some());
1391 }
1392
1393 #[test]
1394 fn test_function_return_types() {
1395 let mut tracker = TypeTracker::empty();
1396
1397 tracker.register_function_return_type("get_point", "Point");
1398
1399 assert_eq!(
1400 tracker.get_function_return_type("get_point"),
1401 Some(&"Point".to_string())
1402 );
1403 assert!(tracker.get_function_return_type("unknown").is_none());
1404 }
1405
1406 #[test]
1407 fn test_typed_field_info() {
1408 let mut registry = TypeSchemaRegistry::new();
1409
1410 TypeSchemaBuilder::new("Vector3")
1411 .f64_field("x")
1412 .f64_field("y")
1413 .f64_field("z")
1414 .register(&mut registry);
1415
1416 let tracker = TypeTracker::new(registry);
1417
1418 let info = tracker.get_typed_field_info("Vector3", "y");
1419 assert!(info.is_some());
1420 let (schema_id, offset, index) = info.unwrap();
1421 assert!(schema_id > 0);
1422 assert_eq!(offset, 8); assert_eq!(index, 1);
1424 }
1425
1426 #[test]
1427 fn test_unknown_type() {
1428 let tracker = TypeTracker::empty();
1429
1430 assert!(!tracker.can_use_typed_access(0, true, "field"));
1432 }
1433
1434 #[test]
1435 fn test_binding_type_tracking() {
1436 let mut registry = TypeSchemaRegistry::new();
1437
1438 TypeSchemaBuilder::new("Config")
1439 .f64_field("threshold")
1440 .string_field("name")
1441 .register(&mut registry);
1442
1443 let mut tracker = TypeTracker::new(registry);
1444
1445 tracker.set_binding_type(5, VariableTypeInfo::named("Config".to_string()));
1447
1448 assert!(tracker.can_use_typed_access(5, false, "threshold"));
1449 assert!(tracker.can_use_typed_access(5, false, "name"));
1450 assert!(!tracker.can_use_typed_access(5, false, "unknown"));
1451 }
1452
1453 #[test]
1454 fn test_storage_hint_inference() {
1455 assert_eq!(
1457 VariableTypeInfo::infer_storage_hint("Number"),
1458 Some(StorageHint::Float64)
1459 );
1460 assert_eq!(
1461 VariableTypeInfo::infer_storage_hint("Integer"),
1462 Some(StorageHint::Int64)
1463 );
1464 assert_eq!(
1465 VariableTypeInfo::infer_storage_hint("Bool"),
1466 Some(StorageHint::Bool)
1467 );
1468 assert_eq!(
1469 VariableTypeInfo::infer_storage_hint("String"),
1470 Some(StorageHint::String)
1471 );
1472
1473 assert_eq!(
1475 VariableTypeInfo::infer_storage_hint("Option<Number>"),
1476 Some(StorageHint::NullableFloat64)
1477 );
1478 assert_eq!(
1479 VariableTypeInfo::infer_storage_hint("Option<Integer>"),
1480 Some(StorageHint::NullableInt64)
1481 );
1482 assert_eq!(
1483 VariableTypeInfo::infer_storage_hint("Option<byte>"),
1484 Some(StorageHint::NullableUInt8)
1485 );
1486 assert_eq!(
1487 VariableTypeInfo::infer_storage_hint("Option<char>"),
1488 Some(StorageHint::NullableInt8)
1489 );
1490 assert_eq!(
1491 VariableTypeInfo::infer_storage_hint("Option<u32>"),
1492 Some(StorageHint::NullableUInt32)
1493 );
1494
1495 assert_eq!(
1498 VariableTypeInfo::infer_storage_hint("SomeCustomType"),
1499 None
1500 );
1501 }
1502
1503 #[test]
1504 fn test_width_integer_storage_hint_inference() {
1505 assert_eq!(
1506 VariableTypeInfo::infer_storage_hint("i8"),
1507 Some(StorageHint::Int8)
1508 );
1509 assert_eq!(
1510 VariableTypeInfo::infer_storage_hint("byte"),
1511 Some(StorageHint::UInt8)
1512 );
1513 assert_eq!(
1514 VariableTypeInfo::infer_storage_hint("char"),
1515 Some(StorageHint::Int8)
1516 );
1517 assert_eq!(
1518 VariableTypeInfo::infer_storage_hint("u16"),
1519 Some(StorageHint::UInt16)
1520 );
1521 assert_eq!(
1522 VariableTypeInfo::infer_storage_hint("i32"),
1523 Some(StorageHint::Int32)
1524 );
1525 assert_eq!(
1526 VariableTypeInfo::infer_storage_hint("u64"),
1527 Some(StorageHint::UInt64)
1528 );
1529 assert_eq!(
1530 VariableTypeInfo::infer_storage_hint("isize"),
1531 Some(StorageHint::IntSize)
1532 );
1533 assert_eq!(
1534 VariableTypeInfo::infer_storage_hint("usize"),
1535 Some(StorageHint::UIntSize)
1536 );
1537 }
1538
1539 #[test]
1540 fn test_concrete_numeric_type_inference() {
1541 assert_eq!(
1542 VariableTypeInfo::infer_numeric_runtime_name("int"),
1543 Some("i64".to_string())
1544 );
1545 assert_eq!(
1546 VariableTypeInfo::infer_numeric_runtime_name("i16"),
1547 Some("i16".to_string())
1548 );
1549 assert_eq!(
1550 VariableTypeInfo::infer_numeric_runtime_name("byte"),
1551 Some("u8".to_string())
1552 );
1553 assert_eq!(
1554 VariableTypeInfo::infer_numeric_runtime_name("Option<f32>"),
1555 Some("f32".to_string())
1556 );
1557 assert_eq!(
1558 VariableTypeInfo::infer_numeric_runtime_name("SomeCustomType"),
1559 None
1560 );
1561 }
1562
1563 #[test]
1564 fn test_native_kind_from_storage_type() {
1565 assert_eq!(
1566 native_kind_from_storage_type(&StorageType::Float64),
1567 Some(NativeKind::Float64)
1568 );
1569 assert_eq!(
1570 native_kind_from_storage_type(&StorageType::NullableFloat64),
1571 Some(NativeKind::NullableFloat64)
1572 );
1573 assert_eq!(
1575 native_kind_from_storage_type(&StorageType::Dynamic),
1576 None
1577 );
1578 }
1579
1580 #[test]
1581 fn test_nullable_number_type() {
1582 let info = VariableTypeInfo::nullable_number();
1583 assert!(info.uses_nan_sentinel());
1584 assert_eq!(info.storage_hint, Some(StorageHint::NullableFloat64));
1585 }
1586
1587 #[test]
1588 fn test_row_view_column_id_resolution() {
1589 let mut registry = TypeSchemaRegistry::new();
1590
1591 TypeSchemaBuilder::new("Candle")
1592 .f64_field("open")
1593 .f64_field("high")
1594 .f64_field("low")
1595 .f64_field("close")
1596 .i64_field("volume")
1597 .register(&mut registry);
1598
1599 let mut tracker = TypeTracker::new(registry);
1600
1601 let schema = tracker.schema_registry().get("Candle").unwrap();
1603 let schema_id = schema.id;
1604
1605 tracker.set_local_type(
1607 0,
1608 VariableTypeInfo::row_view(schema_id, "Candle".to_string()),
1609 );
1610
1611 assert_eq!(tracker.get_row_view_column_id(0, true, "open"), Some(0));
1613 assert_eq!(tracker.get_row_view_column_id(0, true, "high"), Some(1));
1614 assert_eq!(tracker.get_row_view_column_id(0, true, "close"), Some(3));
1615 assert_eq!(tracker.get_row_view_column_id(0, true, "volume"), Some(4));
1616
1617 assert_eq!(tracker.get_row_view_column_id(0, true, "nonexistent"), None);
1619
1620 tracker.set_local_type(1, VariableTypeInfo::named("Candle".to_string()));
1622 assert_eq!(tracker.get_row_view_column_id(1, true, "open"), None);
1623 }
1624
1625 #[test]
1626 fn test_tracker_storage_hints() {
1627 let mut tracker = TypeTracker::empty();
1628
1629 tracker.set_local_type(0, VariableTypeInfo::nullable_number());
1631 assert!(tracker.local_uses_nan_sentinel(0));
1632
1633 tracker.set_local_type(1, VariableTypeInfo::number());
1635 assert!(!tracker.local_uses_nan_sentinel(1));
1636
1637 assert!(!tracker.local_uses_nan_sentinel(99));
1639 }
1640
1641 #[test]
1642 fn test_datatable_type_info() {
1643 let mut registry = TypeSchemaRegistry::new();
1644
1645 TypeSchemaBuilder::new("Trade")
1646 .f64_field("price")
1647 .i64_field("volume")
1648 .string_field("symbol")
1649 .register(&mut registry);
1650
1651 let mut tracker = TypeTracker::new(registry);
1652
1653 let schema = tracker.schema_registry().get("Trade").unwrap();
1654 let schema_id = schema.id;
1655
1656 tracker.set_local_type(
1658 0,
1659 VariableTypeInfo::datatable(schema_id, "Trade".to_string()),
1660 );
1661
1662 let info = tracker.get_local_type(0).unwrap();
1663 assert!(info.is_datatable());
1664 assert!(!info.is_row_view());
1665 assert_eq!(info.schema_id, Some(schema_id));
1666 assert_eq!(info.type_name.as_deref(), Some("Trade"));
1667
1668 tracker.set_local_type(
1670 1,
1671 VariableTypeInfo::row_view(schema_id, "Trade".to_string()),
1672 );
1673 let info = tracker.get_local_type(1).unwrap();
1674 assert!(!info.is_datatable());
1675 assert!(info.is_row_view());
1676 }
1677
1678 #[test]
1679 fn test_v2_struct_layout_registration() {
1680 use shape_value::v2::struct_layout::{FieldKind, StructLayout};
1681
1682 let mut tracker = TypeTracker::empty();
1683
1684 let layout = StructLayout::new(&[("x", FieldKind::F64), ("y", FieldKind::F64)]);
1685 assert_eq!(layout.total_size(), 24);
1686
1687 let schema_id: SchemaId = 42;
1689 tracker.register_v2_layout(schema_id, layout);
1690
1691 let retrieved = tracker.get_v2_layout(schema_id);
1692 assert!(retrieved.is_some());
1693 let retrieved = retrieved.unwrap();
1694 assert_eq!(retrieved.field_count(), 2);
1695 assert_eq!(retrieved.field_offset(0), 8);
1696 assert_eq!(retrieved.field_offset(1), 16);
1697 assert_eq!(retrieved.total_size(), 24);
1698
1699 assert!(tracker.get_v2_layout(999).is_none());
1701 }
1702
1703 #[test]
1704 fn test_v2_typed_array_element_kind() {
1705 use shape_value::v2::struct_layout::FieldKind;
1706
1707 let mut tracker = TypeTracker::empty();
1708
1709 let mut info = VariableTypeInfo::named("Array<number>".to_string());
1711 info.v2_array_element_kind = Some(FieldKind::F64);
1712 tracker.set_local_type(0, info);
1713
1714 assert_eq!(tracker.is_typed_array(0), Some(FieldKind::F64));
1715 assert_eq!(tracker.is_typed_array(1), None); let mut info2 = VariableTypeInfo::named("Array<i32>".to_string());
1719 info2.v2_array_element_kind = Some(FieldKind::I32);
1720 tracker.set_local_type(1, info2);
1721
1722 assert_eq!(tracker.is_typed_array(1), Some(FieldKind::I32));
1723 }
1724
1725 #[test]
1726 fn test_v2_typed_struct_on_variable() {
1727 use shape_value::v2::struct_layout::{FieldKind, StructLayout};
1728
1729 let mut tracker = TypeTracker::empty();
1730
1731 let layout = StructLayout::new(&[
1732 ("name", FieldKind::Ptr),
1733 ("age", FieldKind::I32),
1734 ("score", FieldKind::F64),
1735 ]);
1736 let schema_id: SchemaId = 100;
1737 tracker.register_v2_layout(schema_id, layout);
1738
1739 let mut info = VariableTypeInfo::named("Person".to_string());
1741 info.v2_struct_layout = Some(schema_id);
1742 tracker.set_local_type(0, info);
1743
1744 assert_eq!(tracker.is_typed_struct(0), Some(schema_id));
1746 assert_eq!(tracker.is_typed_struct(1), None);
1747
1748 let layout = tracker.get_v2_layout(schema_id).unwrap();
1750 assert_eq!(layout.field_count(), 3);
1751 assert_eq!(layout.field_kind(0), FieldKind::Ptr);
1752 assert_eq!(layout.field_kind(1), FieldKind::I32);
1753 assert_eq!(layout.field_kind(2), FieldKind::F64);
1754 assert_eq!(layout.heap_field_mask, 0b001); }
1756
1757 #[test]
1758 fn test_v2_fields_default_none() {
1759 let info = VariableTypeInfo::unknown();
1761 assert!(info.v2_array_element_kind.is_none());
1762 assert!(info.v2_struct_layout.is_none());
1763
1764 let info = VariableTypeInfo::number();
1765 assert!(info.v2_array_element_kind.is_none());
1766 assert!(info.v2_struct_layout.is_none());
1767
1768 let info = VariableTypeInfo::named("Foo".to_string());
1769 assert!(info.v2_array_element_kind.is_none());
1770 assert!(info.v2_struct_layout.is_none());
1771
1772 let info = VariableTypeInfo::known(1, "Bar".to_string());
1773 assert!(info.v2_array_element_kind.is_none());
1774 assert!(info.v2_struct_layout.is_none());
1775 }
1776
1777 #[test]
1783 fn test_proof_gap_unresolved_operand_surfaces_cleanly() {
1784 let gap = proof_gap_unresolved_operand(
1785 "emit_typed_arithmetic",
1786 "operand `x` of `Mul` has an unresolved type",
1787 );
1788 assert_eq!(gap.site(), "emit_typed_arithmetic");
1789 assert!(gap.detail().contains("unresolved type"));
1790 let rendered = gap.to_string();
1791 assert!(
1792 rendered.starts_with("E_TYPED_OPCODE_WITHOUT_PROOF at emit_typed_arithmetic:"),
1793 "diagnostic must be the labelled proof-gap form, got: {rendered}"
1794 );
1795 assert!(rendered.contains("operand `x` of `Mul`"));
1796 }
1797}