use crate::{
bytecode::{Instruction, OpCode, Operand},
executor::{ExceptionHandler, VirtualMachine},
executor::vm_impl::stack::drop_with_kind,
};
use shape_runtime::type_schema::builtin_schemas::{
ANYERROR_CATEGORY, ANYERROR_CAUSE, ANYERROR_CODE, ANYERROR_MESSAGE,
ANYERROR_PAYLOAD, ANYERROR_TRACE_INFO,
};
use shape_value::{
HeapKind, KindedSlot, NativeKind, TypedObjectStorage, VMError, ValueSlot,
};
use shape_value::heap_value::{OptionData, ResultData};
use std::sync::Arc;
impl VirtualMachine {
pub(in crate::executor) fn handle_exception(
&mut self,
payload: KindedSlot,
) -> Result<(), VMError> {
if let Some(handler) = self.exception_handlers.pop() {
self.clear_last_uncaught_exception();
for i in handler.stack_size..self.sp {
let (bits, kind) = self.stack_read_kinded_raw(i);
drop_with_kind(bits, kind);
self.stack[i] = Self::NONE_BITS;
self.kinds[i] = NativeKind::Bool;
}
self.sp = handler.stack_size;
self.call_stack.truncate(handler.call_depth);
self.push_kinded_slot(payload)?;
self.ip = handler.catch_ip;
Ok(())
} else {
let message = self.uncaught_error_message(&payload);
drop(payload);
Err(VMError::RuntimeError(message))
}
}
fn uncaught_error_message(&self, payload: &KindedSlot) -> String {
if let NativeKind::Ptr(HeapKind::TypedObject) = payload.kind() {
let bits = payload.slot().raw();
if bits != 0 {
let obj: &TypedObjectStorage =
unsafe { &*(bits as *const TypedObjectStorage) };
if obj.schema_id == self.builtin_schemas.any_error as u64 {
if let Some(msg) = anyerror_message_field(obj) {
return format!("Uncaught error: {}", msg);
}
}
}
}
let formatter = crate::executor::printing::ValueFormatter::new(
&self.program.type_schema_registry,
);
format!("Uncaught error: {}", formatter.format_kinded(payload))
}
#[inline(always)]
pub(in crate::executor) fn exec_exceptions(
&mut self,
instruction: &Instruction,
) -> Result<(), VMError> {
use OpCode::*;
match instruction.opcode {
TypeCheck => self.op_type_check(instruction)?,
SetupTry => self.op_setup_try(instruction)?,
PopHandler => self.op_pop_handler()?,
Throw => self.op_throw()?,
TryUnwrap => self.op_try_unwrap()?,
UnwrapOption => self.op_unwrap_option()?,
ErrorContext => self.op_error_context()?,
IsOk => self.op_is_ok()?,
IsErr => self.op_is_err()?,
UnwrapOk => self.op_unwrap_ok()?,
UnwrapErr => self.op_unwrap_err()?,
_ => unreachable!(
"exec_exceptions called with non-exception opcode: {:?}",
instruction.opcode
),
}
Ok(())
}
pub(in crate::executor) fn op_type_check(
&mut self,
instruction: &Instruction,
) -> Result<(), VMError> {
let (value_bits, value_kind) = self.pop_kinded()?;
let value = KindedSlot::new(ValueSlot::from_raw(value_bits), value_kind);
let annotation = match instruction.operand {
Some(Operand::Const(idx)) => match self.program.constants.get(idx as usize) {
Some(crate::bytecode::Constant::TypeAnnotation(annotation)) => annotation.clone(),
_ => {
drop(value);
return Err(VMError::RuntimeError(
"TypeCheck expects type annotation constant".to_string(),
));
}
},
_ => {
drop(value);
return Err(VMError::InvalidOperand);
}
};
let matches = type_check_kinded(&annotation, &value);
drop(value);
self.push_kinded_slot(KindedSlot::from_bool(matches))
}
pub(in crate::executor) fn op_setup_try(
&mut self,
instruction: &Instruction,
) -> Result<(), VMError> {
if let Some(Operand::Offset(offset)) = instruction.operand {
let catch_ip = (self.ip as i32 + offset) as usize;
self.exception_handlers.push(ExceptionHandler {
catch_ip,
stack_size: self.sp,
call_depth: self.call_stack.len(),
});
Ok(())
} else {
Err(VMError::InvalidOperand)
}
}
pub(in crate::executor) fn op_pop_handler(&mut self) -> Result<(), VMError> {
self.exception_handlers.pop();
Ok(())
}
pub(in crate::executor) fn op_throw(&mut self) -> Result<(), VMError> {
let (error_bits, error_kind) = self.pop_kinded()?;
let raw_payload = KindedSlot::new(ValueSlot::from_raw(error_bits), error_kind);
let payload = self.normalize_err_payload(raw_payload)?;
self.handle_exception(payload)
}
pub(in crate::executor) fn trace_info_full(&mut self) -> Result<KindedSlot, VMError> {
Ok(empty_string_kinded_slot())
}
pub(in crate::executor) fn trace_info_single(&mut self) -> Result<KindedSlot, VMError> {
Ok(empty_string_kinded_slot())
}
pub(in crate::executor) fn build_any_error(
&mut self,
payload: KindedSlot,
cause: Option<KindedSlot>,
trace: KindedSlot,
code: Option<&str>,
) -> Result<KindedSlot, VMError> {
let message_arc = kinded_to_string_arc(payload);
let payload_arc = Arc::clone(&message_arc);
let cause_arc = cause.map(kinded_to_string_arc);
let trace_arc = kinded_to_arc_or_none(trace);
let category_arc = Arc::new("RuntimeError".to_string());
let code_arc = code.map(|s| Arc::new(s.to_string()));
let schema_id = self.builtin_schemas.any_error;
let mut slots: Vec<ValueSlot> = vec![ValueSlot::none(); 6];
let mut heap_mask: u64 = 0;
let mut set_field = |idx: usize, arc: Arc<String>| {
let bits = Arc::into_raw(arc) as u64;
slots[idx] = ValueSlot::from_raw(bits);
heap_mask |= 1u64 << idx;
};
set_field(ANYERROR_CATEGORY, category_arc);
set_field(ANYERROR_PAYLOAD, payload_arc);
if let Some(arc) = cause_arc {
set_field(ANYERROR_CAUSE, arc);
}
if let Some(arc) = trace_arc {
set_field(ANYERROR_TRACE_INFO, arc);
}
set_field(ANYERROR_MESSAGE, message_arc);
if let Some(arc) = code_arc {
set_field(ANYERROR_CODE, arc);
}
let field_kinds: Arc<[NativeKind]> = Arc::from(
vec![NativeKind::String; 6].into_boxed_slice(),
);
let ptr = TypedObjectStorage::_new(
schema_id as u64,
slots.into_boxed_slice(),
heap_mask,
field_kinds,
);
Ok(KindedSlot::from_typed_object_raw(ptr))
}
pub(in crate::executor) fn normalize_err_payload(
&mut self,
payload: KindedSlot,
) -> Result<KindedSlot, VMError> {
if let NativeKind::Ptr(HeapKind::TypedObject) = payload.kind() {
let bits = payload.slot().raw();
if bits != 0 {
let arc: Arc<TypedObjectStorage> =
unsafe { Arc::from_raw(bits as *const _) };
let is_any_error = arc.schema_id == self.builtin_schemas.any_error as u64;
let _ = Arc::into_raw(arc);
if is_any_error {
return Ok(payload);
}
}
}
let trace = self.trace_info_full()?;
self.build_any_error(payload, None, trace, None)
}
pub(in crate::executor) fn op_error_context(&mut self) -> Result<(), VMError> {
let (context_bits, context_kind) = self.pop_kinded()?;
let (value_bits, value_kind) = self.pop_kinded()?;
let context = KindedSlot::new(ValueSlot::from_raw(context_bits), context_kind);
let value = KindedSlot::new(ValueSlot::from_raw(value_bits), value_kind);
if let Some(rd) = read_result(&value)? {
if rd.is_ok {
let inner = rd.payload.clone();
drop(value);
drop(context);
self.push_kinded_slot(inner)
} else {
let inner = rd.payload.clone();
drop(value);
let trace = self.trace_info_full()?;
let any_err = self.build_any_error(inner, Some(context), trace, None)?;
self.handle_exception(any_err)
}
} else if let Some(od) = read_option(&value)? {
if od.is_some {
let inner = od.payload.clone();
drop(value);
drop(context);
self.push_kinded_slot(inner)
} else {
drop(value);
let none_payload = KindedSlot::from_string_arc(Arc::new("None".to_string()));
let trace = self.trace_info_full()?;
let any_err = self.build_any_error(none_payload, Some(context), trace, None)?;
self.handle_exception(any_err)
}
} else if is_null_sentinel(&value) {
drop(value);
let none_payload = KindedSlot::from_string_arc(Arc::new("None".to_string()));
let trace = self.trace_info_full()?;
let any_err = self.build_any_error(none_payload, Some(context), trace, None)?;
self.handle_exception(any_err)
} else {
drop(context);
let kind = value.kind();
let bits = value.slot().raw();
std::mem::forget(value);
self.push_kinded(bits, kind)
}
}
pub(in crate::executor) fn op_try_unwrap(&mut self) -> Result<(), VMError> {
let (bits, kind) = self.pop_kinded()?;
let value = KindedSlot::new(ValueSlot::from_raw(bits), kind);
if let Some(rd) = read_result(&value)? {
if rd.is_ok {
let inner = rd.payload.clone();
drop(value);
self.push_kinded_slot(inner)
} else {
let result_kind = value.kind();
let result_bits = value.slot().raw();
std::mem::forget(value);
self.return_value_inner(result_bits, result_kind)
}
} else if let Some(od) = read_option(&value)? {
if od.is_some {
let inner = od.payload.clone();
drop(value);
self.push_kinded_slot(inner)
} else {
let opt_kind = value.kind();
let opt_bits = value.slot().raw();
std::mem::forget(value);
self.return_value_inner(opt_bits, opt_kind)
}
} else if is_null_sentinel(&value) {
let null_kind = value.kind();
let null_bits = value.slot().raw();
std::mem::forget(value);
self.return_value_inner(null_bits, null_kind)
} else {
let kind = value.kind();
let bits = value.slot().raw();
std::mem::forget(value);
self.push_kinded(bits, kind)
}
}
pub(in crate::executor) fn op_unwrap_option(&mut self) -> Result<(), VMError> {
let (bits, kind) = self.pop_kinded()?;
let value = KindedSlot::new(ValueSlot::from_raw(bits), kind);
let inner = match read_option(&value)? {
Some(od) if od.is_some => od.payload.clone(),
Some(_) => {
drop(value);
return Err(VMError::RuntimeError(
"called UnwrapOption on None value".to_string(),
));
}
None => {
if is_null_sentinel(&value) {
drop(value);
return Err(VMError::RuntimeError(
"called UnwrapOption on null/None value".to_string(),
));
}
let kind = value.kind();
let bits = value.slot().raw();
std::mem::forget(value);
return self.push_kinded(bits, kind);
}
};
drop(value);
self.push_kinded_slot(inner)
}
#[inline(always)]
pub(in crate::executor) fn op_is_ok(&mut self) -> Result<(), VMError> {
let (bits, kind) = self.pop_kinded()?;
let value = KindedSlot::new(ValueSlot::from_raw(bits), kind);
let is_ok = match read_result(&value)? {
Some(rd) => rd.is_ok,
None => false,
};
drop(value);
self.push_kinded_slot(KindedSlot::from_bool(is_ok))
}
#[inline(always)]
pub(in crate::executor) fn op_is_err(&mut self) -> Result<(), VMError> {
let (bits, kind) = self.pop_kinded()?;
let value = KindedSlot::new(ValueSlot::from_raw(bits), kind);
let is_err = match read_result(&value)? {
Some(rd) => !rd.is_ok,
None => false,
};
drop(value);
self.push_kinded_slot(KindedSlot::from_bool(is_err))
}
#[inline(always)]
pub(in crate::executor) fn op_unwrap_ok(&mut self) -> Result<(), VMError> {
let (bits, kind) = self.pop_kinded()?;
let value = KindedSlot::new(ValueSlot::from_raw(bits), kind);
let inner = match read_result(&value)? {
Some(rd) if rd.is_ok => rd.payload.clone(),
Some(_) => {
drop(value);
return Err(VMError::RuntimeError(
"called UnwrapOk on Err value".to_string(),
));
}
None => {
drop(value);
return Err(VMError::RuntimeError(format!(
"UnwrapOk: expected Result, got kind {:?}",
kind
)));
}
};
drop(value);
self.push_kinded_slot(inner)
}
#[inline(always)]
pub(in crate::executor) fn op_unwrap_err(&mut self) -> Result<(), VMError> {
let (bits, kind) = self.pop_kinded()?;
let value = KindedSlot::new(ValueSlot::from_raw(bits), kind);
let inner = match read_result(&value)? {
Some(rd) if !rd.is_ok => rd.payload.clone(),
Some(_) => {
drop(value);
return Err(VMError::RuntimeError(
"called UnwrapErr on Ok value".to_string(),
));
}
None => {
drop(value);
return Err(VMError::RuntimeError(format!(
"UnwrapErr: expected Result, got kind {:?}",
kind
)));
}
};
drop(value);
self.push_kinded_slot(inner)
}
}
#[inline]
fn read_result(slot: &KindedSlot) -> Result<Option<&ResultData>, VMError> {
if !matches!(slot.kind, NativeKind::Ptr(HeapKind::Result)) {
return Ok(None);
}
let bits = slot.slot.raw();
if bits == 0 {
return Err(VMError::RuntimeError(
"Result-kind slot has null payload bits".to_string(),
));
}
let r: &ResultData = unsafe { &*(bits as *const ResultData) };
Ok(Some(r))
}
#[inline]
fn read_option(slot: &KindedSlot) -> Result<Option<&OptionData>, VMError> {
if !matches!(slot.kind, NativeKind::Ptr(HeapKind::Option)) {
return Ok(None);
}
let bits = slot.slot.raw();
if bits == 0 {
return Err(VMError::RuntimeError(
"Option-kind slot has null payload bits".to_string(),
));
}
let o: &OptionData = unsafe { &*(bits as *const OptionData) };
Ok(Some(o))
}
#[inline]
fn is_null_sentinel(slot: &KindedSlot) -> bool {
let bits = slot.slot.raw();
let kind = slot.kind;
match kind {
NativeKind::String | NativeKind::Ptr(_) => bits == 0,
NativeKind::NullableFloat64 => f64::from_bits(bits).is_nan(),
NativeKind::NullableInt8
| NativeKind::NullableInt16
| NativeKind::NullableInt32
| NativeKind::NullableInt64
| NativeKind::NullableIntSize
| NativeKind::NullableUInt8
| NativeKind::NullableUInt16
| NativeKind::NullableUInt32
| NativeKind::NullableUInt64
| NativeKind::NullableUIntSize => bits == 0,
_ => false,
}
}
fn type_check_kinded(
annotation: &shape_ast::ast::TypeAnnotation,
value: &KindedSlot,
) -> bool {
use shape_ast::ast::TypeAnnotation;
match annotation {
TypeAnnotation::Basic(name) => match name.as_str() {
"int" => matches!(
value.kind,
NativeKind::Int8
| NativeKind::Int16
| NativeKind::Int32
| NativeKind::Int64
| NativeKind::IntSize
| NativeKind::UInt8
| NativeKind::UInt16
| NativeKind::UInt32
| NativeKind::UInt64
| NativeKind::UIntSize
),
"number" | "float" => matches!(value.kind, NativeKind::Float64),
"bool" => matches!(value.kind, NativeKind::Bool) && value.slot.raw() != 0
|| matches!(value.kind, NativeKind::Bool),
"string" => matches!(
value.kind,
NativeKind::String | NativeKind::Ptr(HeapKind::String)
),
"char" => matches!(value.kind, NativeKind::Ptr(HeapKind::Char)),
"array" => matches!(value.kind, NativeKind::Ptr(HeapKind::TypedArray)),
"object" => matches!(value.kind, NativeKind::Ptr(HeapKind::TypedObject)),
_ => false,
},
TypeAnnotation::Null => value.slot.raw() == 0,
TypeAnnotation::Generic { name, .. } => match name.as_str() {
"Result" => matches!(value.kind, NativeKind::Ptr(HeapKind::Result)),
"Option" => {
matches!(value.kind, NativeKind::Ptr(HeapKind::Option))
|| value.slot.raw() == 0
}
"Array" | "Vec" => matches!(value.kind, NativeKind::Ptr(HeapKind::TypedArray)),
"HashMap" | "Map" => matches!(value.kind, NativeKind::Ptr(HeapKind::HashMap)),
"HashSet" | "Set" => matches!(value.kind, NativeKind::Ptr(HeapKind::HashSet)),
"Iterator" => matches!(value.kind, NativeKind::Ptr(HeapKind::Iterator)),
_ => false,
},
_ => false,
}
}
#[inline]
fn empty_string_kinded_slot() -> KindedSlot {
KindedSlot::new(ValueSlot::none(), NativeKind::String)
}
fn kinded_to_string_arc(slot: KindedSlot) -> Arc<String> {
if matches!(slot.kind(), NativeKind::String) {
let bits = slot.slot().raw();
if bits != 0 {
let arc: Arc<String> =
unsafe { Arc::from_raw(bits as *const String) };
std::mem::forget(slot);
return arc;
}
return Arc::new(String::new());
}
let text = stringify_non_string_kinded(&slot);
drop(slot);
Arc::new(text)
}
fn anyerror_message_field(obj: &TypedObjectStorage) -> Option<String> {
let idx = ANYERROR_MESSAGE;
if (obj.heap_mask >> idx) & 1 == 0 {
return None;
}
let bits = obj.slots.get(idx)?.raw();
if bits == 0 {
return None;
}
let s: &String = unsafe { &*(bits as *const String) };
Some(s.clone())
}
fn kinded_to_arc_or_none(slot: KindedSlot) -> Option<Arc<String>> {
if matches!(slot.kind(), NativeKind::String) && slot.slot().raw() == 0 {
return None;
}
Some(kinded_to_string_arc(slot))
}
fn stringify_non_string_kinded(slot: &KindedSlot) -> String {
match slot.kind() {
NativeKind::Bool => slot.slot().as_bool().to_string(),
NativeKind::Int8
| NativeKind::Int16
| NativeKind::Int32
| NativeKind::Int64
| NativeKind::IntSize
| NativeKind::NullableInt8
| NativeKind::NullableInt16
| NativeKind::NullableInt32
| NativeKind::NullableInt64
| NativeKind::NullableIntSize => slot.slot().as_i64().to_string(),
NativeKind::UInt8
| NativeKind::UInt16
| NativeKind::UInt32
| NativeKind::UInt64
| NativeKind::UIntSize
| NativeKind::NullableUInt8
| NativeKind::NullableUInt16
| NativeKind::NullableUInt32
| NativeKind::NullableUInt64
| NativeKind::NullableUIntSize => slot.slot().as_u64().to_string(),
NativeKind::Float64 | NativeKind::NullableFloat64 => {
slot.slot().as_f64().to_string()
}
other => format!("<error payload kind={:?}>", other),
}
}
#[cfg(test)]
mod build_any_error_tests {
use super::*;
use crate::executor::VMConfig;
use shape_value::heap_value::TypedObjectStorage;
#[test]
fn build_any_error_message_reads_back() {
let mut vm = VirtualMachine::new(VMConfig::default());
let payload = KindedSlot::from_string_arc(Arc::new("boom".to_string()));
let trace = empty_string_kinded_slot();
let result = vm.build_any_error(payload, None, trace, None).unwrap();
assert_eq!(result.kind(), NativeKind::Ptr(HeapKind::TypedObject));
let bits = result.slot().raw();
assert!(bits != 0, "AnyError TypedObject pointer should be non-null");
let storage: Arc<TypedObjectStorage> =
unsafe { Arc::from_raw(bits as *const _) };
assert_eq!(storage.schema_id, vm.builtin_schemas.any_error as u64);
assert_eq!(storage.slots.len(), 6);
assert_eq!(storage.field_kinds.len(), 6);
for k in storage.field_kinds.iter() {
assert_eq!(*k, NativeKind::String);
}
let msg_bits = storage.slots[ANYERROR_MESSAGE].raw();
assert!(msg_bits != 0);
let msg_str: &String = unsafe { &*(msg_bits as *const String) };
assert_eq!(msg_str.as_str(), "boom");
let cat_bits = storage.slots[ANYERROR_CATEGORY].raw();
let cat_str: &String = unsafe { &*(cat_bits as *const String) };
assert_eq!(cat_str.as_str(), "RuntimeError");
assert_eq!(storage.slots[ANYERROR_CAUSE].raw(), 0);
assert_eq!((storage.heap_mask >> ANYERROR_CAUSE) & 1, 0);
let _ = Arc::into_raw(storage);
drop(result);
}
#[test]
fn normalize_err_payload_wraps_string() {
let mut vm = VirtualMachine::new(VMConfig::default());
let raw = KindedSlot::from_string_arc(Arc::new("oops".to_string()));
let wrapped = vm.normalize_err_payload(raw).unwrap();
assert_eq!(wrapped.kind(), NativeKind::Ptr(HeapKind::TypedObject));
let bits = wrapped.slot().raw();
let storage: Arc<TypedObjectStorage> =
unsafe { Arc::from_raw(bits as *const _) };
let msg_bits = storage.slots[ANYERROR_MESSAGE].raw();
let msg_str: &String = unsafe { &*(msg_bits as *const String) };
assert_eq!(msg_str.as_str(), "oops");
let _ = Arc::into_raw(storage);
drop(wrapped);
}
#[test]
fn normalize_err_payload_already_anyerror_passthrough() {
let mut vm = VirtualMachine::new(VMConfig::default());
let raw = KindedSlot::from_string_arc(Arc::new("inner".to_string()));
let first = vm.normalize_err_payload(raw).unwrap();
let first_bits = first.slot().raw();
let again = vm.normalize_err_payload(first).unwrap();
assert_eq!(again.slot().raw(), first_bits);
assert_eq!(again.kind(), NativeKind::Ptr(HeapKind::TypedObject));
drop(again);
}
}
#[cfg(test)]
mod variant_codegen_tests {
use super::*;
use crate::executor::VMConfig;
#[test]
fn smoke_ok_int_is_ok_then_unwrap() {
let inner = KindedSlot::from_int(42);
let result_data = Arc::new(shape_value::heap_value::ResultData::ok(inner));
let ok_carrier = KindedSlot::from_result(result_data);
let is_ok_value = match read_result(&ok_carrier).unwrap() {
Some(rd) => rd.is_ok,
None => panic!("Result kind read failed"),
};
assert!(is_ok_value);
let unwrapped = match read_result(&ok_carrier).unwrap() {
Some(rd) if rd.is_ok => rd.payload.clone(),
_ => panic!("Ok unwrap failed"),
};
assert_eq!(unwrapped.as_i64(), Some(42));
drop(ok_carrier);
drop(unwrapped);
}
#[test]
fn err_string_is_err_then_unwrap() {
let inner = KindedSlot::from_string_arc(Arc::new("oops".to_string()));
let result_data = Arc::new(shape_value::heap_value::ResultData::err(inner));
let err_carrier = KindedSlot::from_result(result_data);
let is_ok_value = match read_result(&err_carrier).unwrap() {
Some(rd) => rd.is_ok,
None => panic!("Result kind read failed"),
};
assert!(!is_ok_value);
let unwrapped = match read_result(&err_carrier).unwrap() {
Some(rd) if !rd.is_ok => rd.payload.clone(),
_ => panic!("Err unwrap failed"),
};
assert_eq!(unwrapped.as_str(), Some("oops"));
drop(err_carrier);
drop(unwrapped);
}
#[test]
fn some_int_unwrap_option() {
let inner = KindedSlot::from_int(42);
let opt_data = Arc::new(shape_value::heap_value::OptionData::some(inner));
let some_carrier = KindedSlot::from_option(opt_data);
let unwrapped = match read_option(&some_carrier).unwrap() {
Some(od) if od.is_some => od.payload.clone(),
_ => panic!("Some unwrap failed"),
};
assert_eq!(unwrapped.as_i64(), Some(42));
drop(some_carrier);
drop(unwrapped);
}
#[test]
fn none_carrier_is_some_false() {
let opt_data = Arc::new(shape_value::heap_value::OptionData::none());
let none_carrier = KindedSlot::from_option(opt_data);
let is_some_value = match read_option(&none_carrier).unwrap() {
Some(od) => od.is_some,
None => panic!("Option kind read failed"),
};
assert!(!is_some_value);
drop(none_carrier);
}
#[test]
fn err_carrier_drop_is_balanced() {
let inner_arc = Arc::new("oops".to_string());
let strong_before = Arc::strong_count(&inner_arc);
let payload = KindedSlot::from_string_arc(Arc::clone(&inner_arc));
assert_eq!(Arc::strong_count(&inner_arc), strong_before + 1);
let result_data = Arc::new(shape_value::heap_value::ResultData::err(payload));
let err_carrier = KindedSlot::from_result(result_data);
assert_eq!(Arc::strong_count(&inner_arc), strong_before + 1);
drop(err_carrier);
assert_eq!(Arc::strong_count(&inner_arc), strong_before);
}
#[test]
fn type_check_result_matches_ok_carrier() {
use shape_ast::ast::TypeAnnotation;
use shape_ast::ast::TypePath;
let result_data = Arc::new(shape_value::heap_value::ResultData::ok(
KindedSlot::from_int(42),
));
let carrier = KindedSlot::from_result(result_data);
let annotation = TypeAnnotation::Generic {
name: TypePath::simple("Result"),
args: vec![
TypeAnnotation::Basic("int".to_string()),
TypeAnnotation::Basic("string".to_string()),
],
};
assert!(type_check_kinded(&annotation, &carrier));
drop(carrier);
}
#[test]
fn type_check_option_matches_some_and_null() {
use shape_ast::ast::TypeAnnotation;
use shape_ast::ast::TypePath;
let some_carrier = KindedSlot::from_option(Arc::new(
shape_value::heap_value::OptionData::some(KindedSlot::from_int(7)),
));
let annotation = TypeAnnotation::Generic {
name: TypePath::simple("Option"),
args: vec![TypeAnnotation::Basic("int".to_string())],
};
assert!(type_check_kinded(&annotation, &some_carrier));
let null_carrier = KindedSlot::none();
assert!(type_check_kinded(&annotation, &null_carrier));
drop(some_carrier);
drop(null_carrier);
}
#[test]
fn read_result_on_non_result_returns_none() {
let int_carrier = KindedSlot::from_int(42);
let result = read_result(&int_carrier).unwrap();
assert!(result.is_none());
drop(int_carrier);
}
}
#[cfg(test)]
#[cfg(feature = "phase-2c-exception-rebuild")]
mod unwrap_refcount_regression_tests {
use crate::test_utils::eval;
#[test]
fn match_ok_small_string_then_len_no_heap_corruption() {
let v = eval(
r#"
let encoded: Result<string, string> = Ok("hello")
match encoded {
Ok(data) => data.len(),
Err(_) => 0,
}
"#,
);
assert_eq!(v.as_i64(), Some(5));
}
#[test]
fn match_err_small_string_then_len_no_heap_corruption() {
let v = eval(
r#"
let encoded: Result<int, string> = Err("oops!")
match encoded {
Ok(_) => 0,
Err(msg) => msg.len(),
}
"#,
);
assert_eq!(v.as_i64(), Some(5));
}
}
#[cfg(test)]
mod ws3_uncaught_and_array_typecheck_tests {
use crate::test_utils::eval_result;
#[test]
fn ws3_f4_uncaught_match_failure_is_clean_no_jargon() {
let err = eval_result(
r#"
fn run() -> int {
let x = 5
match x { 1 => 10, 2 => 20 }
}
run()
"#,
)
.expect_err("non-exhaustive match must surface a runtime error");
let msg = format!("{}", err);
assert!(
msg.contains("Uncaught error:"),
"expected a clean `Uncaught error:` message, got: {}",
msg
);
assert!(
!msg.contains("phase-2c")
&& !msg.contains("ADR-006")
&& !msg.contains("ValueWord")
&& !msg.contains("D-raw-helpers"),
"uncaught-exception message must not dump internal jargon: {}",
msg
);
}
#[test]
fn ws3_non_rest_array_destructure_runs_cleanly() {
let result = eval_result(
r#"
fn run() -> int {
let xs = [10, 20, 30]
let [a, b, c] = xs
b
}
run()
"#,
);
let slot = result.expect("plain array destructure must not raise");
assert_eq!(slot.as_i64(), Some(20));
}
}