use crate::bytecode::BytecodeProgram;
use crate::compiler::BytecodeCompiler;
use crate::executor::{VMConfig, VirtualMachine};
use shape_ast::ast::{
AnnotationHandlerParam, DestructurePattern, Expr, FunctionDef, FunctionParameter, Item,
ObjectEntry, ObjectTypeField, Program, Span, Statement, TypeAnnotation, VarKind,
VariableDecl,
};
use shape_ast::error::{Result, ShapeError};
use shape_value::heap_value::{HeapKind, HeapValue};
use shape_value::{KindedSlot, NativeKind};
use std::sync::Arc;
use std::sync::atomic::{AtomicU8, Ordering};
const COMPTIME_BUILTIN_FORWARDERS: &[(&str, usize, &str, Option<&[&str]>)] = &[
("implements", 2, "implements", None),
("warning", 1, "warning", None),
("error", 1, "error", None),
(
"build_config",
0,
"build_config",
Some(&["debug", "target_arch", "target_os", "version"]),
),
("type_info", 1, "type_info", Some(&["kind", "name"])),
];
pub(crate) struct ComptimeExecutionResult {
pub value: KindedSlot,
pub directives: Vec<super::comptime_builtins::ComptimeDirective>,
}
fn comptime_target_param_type() -> TypeAnnotation {
TypeAnnotation::Object(vec![
ObjectTypeField {
name: "kind".to_string(),
optional: false,
type_annotation: TypeAnnotation::Basic("string".to_string()),
annotations: vec![],
},
ObjectTypeField {
name: "name".to_string(),
optional: false,
type_annotation: TypeAnnotation::Basic("string".to_string()),
annotations: vec![],
},
ObjectTypeField {
name: "fields".to_string(),
optional: false,
type_annotation: TypeAnnotation::Array(Box::new(TypeAnnotation::Basic(
"unknown".to_string(),
))),
annotations: vec![],
},
ObjectTypeField {
name: "params".to_string(),
optional: false,
type_annotation: TypeAnnotation::Array(Box::new(TypeAnnotation::Basic(
"unknown".to_string(),
))),
annotations: vec![],
},
ObjectTypeField {
name: "return_type".to_string(),
optional: true,
type_annotation: TypeAnnotation::Basic("string".to_string()),
annotations: vec![],
},
ObjectTypeField {
name: "annotations".to_string(),
optional: false,
type_annotation: TypeAnnotation::Array(Box::new(TypeAnnotation::Basic(
"unknown".to_string(),
))),
annotations: vec![],
},
ObjectTypeField {
name: "captures".to_string(),
optional: false,
type_annotation: TypeAnnotation::Array(Box::new(TypeAnnotation::Basic(
"unknown".to_string(),
))),
annotations: vec![],
},
])
}
fn comptime_builtin_forwarders() -> Vec<Item> {
COMPTIME_BUILTIN_FORWARDERS
.iter()
.map(|(name, arity, target_method, return_fields)| {
let params: Vec<shape_ast::ast::FunctionParameter> = (0..*arity)
.map(|i| shape_ast::ast::FunctionParameter {
pattern: shape_ast::ast::DestructurePattern::Identifier(
format!("arg{}", i),
Span::DUMMY,
),
is_const: false,
is_reference: false,
is_mut_reference: false,
is_out: false,
type_annotation: None,
default_value: None,
})
.collect();
let args: Vec<Expr> = (0..*arity)
.map(|i| Expr::Identifier(format!("arg{}", i), Span::DUMMY))
.collect();
let body_expr = Expr::QualifiedFunctionCall {
namespace: "__comptime__".to_string(),
function: (*target_method).to_string(),
args,
named_args: Vec::new(),
span: Span::DUMMY,
};
let return_type = return_fields.map(|fields| {
TypeAnnotation::Object(
fields
.iter()
.map(|f| ObjectTypeField {
name: f.to_string(),
optional: false,
type_annotation: TypeAnnotation::Basic("unknown".to_string()),
annotations: vec![],
})
.collect(),
)
});
Item::Function(
FunctionDef {
name: (*name).to_string(),
name_span: Span::DUMMY,
declaring_module_path: None,
doc_comment: None,
params,
return_type,
body: vec![Statement::Return(Some(body_expr), Span::DUMMY)],
type_params: Some(Vec::new()),
annotations: Vec::new(),
where_clause: None,
is_async: false,
is_comptime: false,
},
Span::DUMMY,
)
})
.collect()
}
fn ensure_tail_return(body: &mut Vec<Statement>) {
let Some(last) = body.last_mut() else {
return;
};
match last {
Statement::If(if_stmt, _span) => {
ensure_tail_return(&mut if_stmt.then_body);
if let Some(else_body) = &mut if_stmt.else_body {
ensure_tail_return(else_body);
}
}
Statement::Expression(expr, span) => {
*last = Statement::Return(Some(expr.clone()), *span);
}
Statement::Return(_, _) => {}
_ => {}
}
}
fn rewrite_implements_ident_args(stmt: &mut Statement) {
match stmt {
Statement::Expression(expr, _) | Statement::Return(Some(expr), _) => {
rewrite_implements_in_expr(expr);
}
Statement::VariableDecl(decl, _) => {
if let Some(init) = &mut decl.value {
rewrite_implements_in_expr(init);
}
}
Statement::If(if_stmt, _) => {
for s in &mut if_stmt.then_body {
rewrite_implements_ident_args(s);
}
if let Some(else_body) = &mut if_stmt.else_body {
for s in else_body {
rewrite_implements_ident_args(s);
}
}
}
_ => {}
}
}
fn rewrite_implements_in_expr(expr: &mut Expr) {
if let Expr::FunctionCall { name, args, .. } = expr {
if name == "implements" {
for arg in args.iter_mut() {
if let Expr::Identifier(ident, span) = arg {
*arg = Expr::Literal(shape_ast::ast::Literal::String(ident.clone()), *span);
}
}
}
}
}
fn rewrite_type_info_ident_args(stmt: &mut Statement) {
match stmt {
Statement::Expression(expr, _) | Statement::Return(Some(expr), _) => {
rewrite_type_info_in_expr(expr);
}
Statement::VariableDecl(decl, _) => {
if let Some(init) = &mut decl.value {
rewrite_type_info_in_expr(init);
}
}
Statement::If(if_stmt, _) => {
for s in &mut if_stmt.then_body {
rewrite_type_info_ident_args(s);
}
if let Some(else_body) = &mut if_stmt.else_body {
for s in else_body {
rewrite_type_info_ident_args(s);
}
}
}
_ => {}
}
}
fn rewrite_type_info_in_expr(expr: &mut Expr) {
if let Expr::FunctionCall { name, args, .. } = expr {
if name == "type_info" {
for arg in args.iter_mut() {
if let Expr::Identifier(ident, span) = arg {
*arg = Expr::Literal(shape_ast::ast::Literal::String(ident.clone()), *span);
}
}
}
}
}
pub(crate) fn execute_comptime(
statements: &[Statement],
comptime_helpers: &[FunctionDef],
extensions: &[shape_runtime::module_exports::ModuleExports],
trait_impl_keys: std::collections::HashSet<String>,
known_type_symbols: std::collections::HashSet<String>,
type_snapshot: super::comptime_builtins::TypeReflectionSnapshot,
) -> Result<ComptimeExecutionResult> {
execute_comptime_with_context(
statements,
comptime_helpers,
&[],
&[],
&[],
extensions,
trait_impl_keys,
known_type_symbols,
type_snapshot,
)
}
pub(crate) fn execute_comptime_with_context(
statements: &[Statement],
comptime_helpers: &[FunctionDef],
comptime_impl_blocks: &[shape_ast::ast::types::ImplBlock],
comptime_context_trait_defs: &[shape_ast::ast::types::TraitDef],
comptime_context_struct_defs: &[shape_ast::ast::types::StructTypeDef],
extensions: &[shape_runtime::module_exports::ModuleExports],
trait_impl_keys: std::collections::HashSet<String>,
known_type_symbols: std::collections::HashSet<String>,
type_snapshot: super::comptime_builtins::TypeReflectionSnapshot,
) -> Result<ComptimeExecutionResult> {
let mut body = statements.to_vec();
for stmt in &mut body {
rewrite_implements_ident_args(stmt);
rewrite_type_info_ident_args(stmt);
}
ensure_tail_return(&mut body);
let func_name = "__comptime_block__".to_string();
let func_def = FunctionDef {
name: func_name.clone(),
name_span: Span::DUMMY,
declaring_module_path: None,
doc_comment: None,
params: Vec::new(),
return_type: None,
body,
type_params: Some(Vec::new()),
annotations: Vec::new(),
where_clause: None,
is_async: false,
is_comptime: false,
};
let mut items = comptime_builtin_forwarders();
for trait_def in comptime_context_trait_defs {
items.push(Item::Trait(trait_def.clone(), Span::DUMMY));
}
for struct_def in comptime_context_struct_defs {
items.push(Item::StructType(struct_def.clone(), Span::DUMMY));
}
for impl_block in comptime_impl_blocks {
let mut block = impl_block.clone();
block.is_comptime = false;
items.push(Item::Impl(block, Span::DUMMY));
}
items.extend(
comptime_helpers
.iter()
.cloned()
.map(|helper| Item::Function(helper, Span::DUMMY)),
);
items.push(Item::Function(func_def, Span::DUMMY));
items.push(Item::Expression(
Expr::FunctionCall {
name: func_name,
args: Vec::new(),
named_args: Vec::new(),
span: Span::DUMMY,
},
Span::DUMMY,
));
let program = Program {
items,
docs: shape_ast::ast::ProgramDocs::default(),
};
compile_and_execute_comptime_program(
&program,
vec!["__comptime__".to_string()],
Vec::new(),
extensions,
trait_impl_keys,
known_type_symbols,
type_snapshot,
)
}
fn compile_and_execute_comptime_program(
program: &Program,
mut known_bindings: Vec<String>,
mut runtime_module_bindings: Vec<(String, KindedSlot)>,
extensions: &[shape_runtime::module_exports::ModuleExports],
trait_impl_keys: std::collections::HashSet<String>,
known_type_symbols: std::collections::HashSet<String>,
type_snapshot: super::comptime_builtins::TypeReflectionSnapshot,
) -> Result<ComptimeExecutionResult> {
let comptime_builtins =
super::comptime_builtins::create_comptime_builtins_module(trait_impl_keys, type_snapshot);
let mut all_extensions: Vec<shape_runtime::module_exports::ModuleExports> = extensions.to_vec();
all_extensions.push(comptime_builtins);
for module in &all_extensions {
if !known_bindings.iter().any(|name| name == &module.name) {
known_bindings.push(module.name.clone());
}
}
let mut compiler = BytecodeCompiler::new().with_extensions(all_extensions.clone());
compiler.set_comptime_mode(true);
compiler.allow_internal_comptime_namespace = true;
compiler.register_known_bindings(&known_bindings);
for type_name in known_type_symbols {
compiler
.struct_types
.entry(type_name)
.or_insert_with(|| (Vec::new(), Span::DUMMY));
}
let mut bytecode = compiler.compile(program)?;
rebind_typed_object_bindings_to_bytecode_schemas(&bytecode, &mut runtime_module_bindings);
for module in &all_extensions {
ensure_module_object_schema(&mut bytecode, module);
}
execute_in_runtime_with_module_bindings(bytecode, &all_extensions, runtime_module_bindings)
}
fn rebind_typed_object_bindings_to_bytecode_schemas(
bytecode: &BytecodeProgram,
module_bindings: &mut [(String, KindedSlot)],
) {
use shape_value::TypedObjectStorage;
use shape_value::ValueSlot;
for (_name, value) in module_bindings.iter_mut() {
if !matches!(
value.kind(),
NativeKind::Ptr(HeapKind::TypedObject)
) {
continue;
}
let src_bits = value.slot().raw();
if src_bits == 0 {
continue;
}
let src_storage: &TypedObjectStorage =
unsafe { &*(src_bits as *const TypedObjectStorage) };
let src_schema_id = src_storage.schema_id as shape_runtime::type_schema::SchemaId;
let Some(src_schema) =
shape_runtime::type_schema::lookup_schema_by_id_public(src_schema_id)
else {
continue;
};
let src_field_names: Vec<&str> =
src_schema.fields.iter().map(|f| f.name.as_str()).collect();
let target_schema = bytecode
.type_schema_registry
.type_names()
.filter_map(|name| bytecode.type_schema_registry.get(name))
.filter(|schema| {
src_field_names
.iter()
.all(|name| schema.get_field(name).is_some())
})
.min_by_key(|schema| schema.fields.len())
.cloned();
let Some(target_schema) = target_schema else {
continue;
};
if target_schema.id == src_schema_id {
continue;
}
let n = target_schema.fields.len();
let mut new_slots: Vec<ValueSlot> = Vec::with_capacity(n);
let mut new_kinds: Vec<NativeKind> = Vec::with_capacity(n);
let mut new_heap_mask: u64 = 0;
let mut bail = false;
for (target_idx, target_field) in target_schema.fields.iter().enumerate() {
let Some(src_field) = src_schema.get_field(&target_field.name) else {
bail = true;
break;
};
let src_idx = src_field.index as usize;
if src_idx >= src_storage.slots.len() || src_idx >= src_storage.field_kinds.len() {
bail = true;
break;
}
let src_kind = src_storage.field_kinds[src_idx];
let src_slot = src_storage.slots[src_idx];
let kinded = read_typed_object_field(
src_slot,
src_kind,
src_storage.heap_mask,
src_idx,
);
let bits = kinded.slot().raw();
let kind = kinded.kind();
std::mem::forget(kinded);
new_slots.push(ValueSlot::from_raw(bits));
new_kinds.push(kind);
let is_heap_kind = matches!(kind, NativeKind::String | NativeKind::Ptr(_));
if is_heap_kind && bits != 0 && target_idx < 64 {
new_heap_mask |= 1u64 << target_idx;
}
}
if bail {
for (i, slot) in new_slots.drain(..).enumerate() {
let kind = new_kinds[i];
drop(KindedSlot::new(slot, kind));
}
continue;
}
let new_ptr = TypedObjectStorage::_new(
target_schema.id as u64,
new_slots.into_boxed_slice(),
new_heap_mask,
Arc::from(new_kinds.into_boxed_slice()),
);
let new_kinded = KindedSlot::new(
ValueSlot::from_typed_object_raw(new_ptr),
NativeKind::Ptr(HeapKind::TypedObject),
);
let old = std::mem::replace(value, new_kinded);
drop(old);
}
}
fn ensure_module_object_schema(
bytecode: &mut BytecodeProgram,
module: &shape_runtime::module_exports::ModuleExports,
) {
let schema_name = format!("__mod_{}", module.name);
if bytecode.type_schema_registry.get(&schema_name).is_some() {
return;
}
let mut export_names: Vec<String> = module
.export_names_available(true)
.into_iter()
.map(|name| name.to_string())
.collect();
export_names.sort();
export_names.dedup();
let fields: Vec<(String, shape_runtime::type_schema::FieldType)> = export_names
.into_iter()
.map(|name| (name, shape_runtime::type_schema::FieldType::Any))
.collect();
bytecode
.type_schema_registry
.register_type(schema_name, fields);
}
#[cfg(test)]
pub(crate) fn execute_comptime_with_target(
handler_body: &Expr,
handler_param: &str,
target_value: KindedSlot,
extensions: &[shape_runtime::module_exports::ModuleExports],
trait_impl_keys: std::collections::HashSet<String>,
known_type_symbols: std::collections::HashSet<String>,
) -> Result<ComptimeExecutionResult> {
let handler_params = vec![AnnotationHandlerParam {
name: handler_param.to_string(),
is_variadic: false,
}];
execute_comptime_with_annotation_handler(
handler_body,
&handler_params,
target_value,
&[],
&[],
&[],
&[],
extensions,
trait_impl_keys,
known_type_symbols,
)
}
pub(crate) fn execute_comptime_with_annotation_handler(
handler_body: &Expr,
handler_params: &[AnnotationHandlerParam],
target_value: KindedSlot,
annotation_args: &[Expr],
annotation_def_param_names: &[String],
const_bindings: &[(String, KindedSlot)],
comptime_helpers: &[FunctionDef],
extensions: &[shape_runtime::module_exports::ModuleExports],
trait_impl_keys: std::collections::HashSet<String>,
known_type_symbols: std::collections::HashSet<String>,
) -> Result<ComptimeExecutionResult> {
if handler_params.iter().filter(|p| p.is_variadic).count() > 1 {
return Err(ShapeError::RuntimeError {
message: "comptime annotation handlers support at most one variadic parameter"
.to_string(),
location: None,
});
}
if let Some((idx, _)) = handler_params
.iter()
.enumerate()
.find(|(_, p)| p.is_variadic)
{
if idx != handler_params.len().saturating_sub(1) {
return Err(ShapeError::RuntimeError {
message: "variadic comptime annotation handler parameter must be last".to_string(),
location: None,
});
}
}
let params: Vec<FunctionParameter> = handler_params
.iter()
.enumerate()
.map(|(idx, p)| FunctionParameter {
pattern: DestructurePattern::Identifier(p.name.clone(), Span::DUMMY),
is_const: false,
is_reference: false,
is_mut_reference: false,
is_out: false,
type_annotation: if idx == 0 {
Some(comptime_target_param_type())
} else if idx == 1 {
Some(TypeAnnotation::Object(Vec::new()))
} else {
None
},
default_value: None,
})
.collect();
let mut call_args: Vec<Expr> = Vec::with_capacity(handler_params.len());
let mut ann_idx = 0usize;
for (idx, param) in handler_params.iter().enumerate() {
if idx == 0 {
call_args.push(Expr::Identifier("__target_arg__".to_string(), Span::DUMMY));
continue;
}
if idx == 1 {
call_args.push(Expr::Identifier("__ctx_arg__".to_string(), Span::DUMMY));
continue;
}
if param.is_variadic {
call_args.push(Expr::Array(
annotation_args.get(ann_idx..).unwrap_or_default().to_vec(),
Span::DUMMY,
));
ann_idx = annotation_args.len();
continue;
}
let Some(arg) = annotation_args.get(ann_idx) else {
return Err(ShapeError::RuntimeError {
message: format!(
"missing annotation argument for comptime handler parameter '{}'",
param.name
),
location: None,
});
};
call_args.push(arg.clone());
ann_idx += 1;
}
let extra_handler_params = handler_params.len().saturating_sub(2);
if extra_handler_params > 0
&& ann_idx < annotation_args.len()
&& !handler_params.iter().any(|p| p.is_variadic)
{
return Err(ShapeError::RuntimeError {
message: format!(
"too many annotation arguments: expected {}, got {}",
ann_idx,
annotation_args.len()
),
location: None,
});
}
let mut params = params;
if extra_handler_params == 0 && !annotation_def_param_names.is_empty() {
for (i, def_param_name) in annotation_def_param_names.iter().enumerate() {
if let Some(arg) = annotation_args.get(i) {
params.push(FunctionParameter {
pattern: DestructurePattern::Identifier(def_param_name.clone(), Span::DUMMY),
is_const: false,
is_reference: false,
is_mut_reference: false,
is_out: false,
type_annotation: None,
default_value: None,
});
call_args.push(arg.clone());
}
}
}
let ctx_nb = shape_runtime::type_schema::typed_object_from_pairs(&[]);
let func_name = "__comptime_handler_fn__".to_string();
let func_def = FunctionDef {
name: func_name.clone(),
name_span: Span::DUMMY,
declaring_module_path: None,
doc_comment: None,
params,
return_type: None,
body: vec![Statement::Return(Some(handler_body.clone()), Span::DUMMY)],
type_params: Some(Vec::new()),
annotations: Vec::new(),
where_clause: None,
is_async: false,
is_comptime: false,
};
let mut items = comptime_builtin_forwarders();
items.extend(
comptime_helpers
.iter()
.cloned()
.map(|helper| Item::Function(helper, Span::DUMMY)),
);
for (name, value) in const_bindings {
let expr = nb_to_expr(value, Span::DUMMY).map_err(|message| ShapeError::RuntimeError {
message: format!(
"failed to materialize comptime const binding '{}': {}",
name, message
),
location: None,
})?;
items.push(Item::VariableDecl(
VariableDecl {
kind: VarKind::Const,
is_mut: false,
pattern: DestructurePattern::Identifier(name.clone(), Span::DUMMY),
type_annotation: None,
value: Some(expr),
ownership: Default::default(),
},
Span::DUMMY,
));
}
items.push(Item::Function(func_def, Span::DUMMY));
items.push(Item::Expression(
Expr::FunctionCall {
name: func_name,
args: call_args,
named_args: Vec::new(),
span: Span::DUMMY,
},
Span::DUMMY,
));
let program = Program {
items,
docs: shape_ast::ast::ProgramDocs::default(),
};
compile_and_execute_comptime_program(
&program,
vec![
"__target_arg__".to_string(),
"__ctx_arg__".to_string(),
"__comptime__".to_string(),
],
vec![
("__target_arg__".to_string(), target_value),
("__ctx_arg__".to_string(), ctx_nb),
],
extensions,
trait_impl_keys,
known_type_symbols,
super::comptime_builtins::TypeReflectionSnapshot::default(),
)
}
fn execute_in_runtime_with_module_bindings(
bytecode: BytecodeProgram,
extensions: &[shape_runtime::module_exports::ModuleExports],
module_bindings: Vec<(String, KindedSlot)>,
) -> Result<ComptimeExecutionResult> {
let run = |module_bindings: Vec<(String, KindedSlot)>| -> Result<ComptimeExecutionResult> {
let mut vm = VirtualMachine::new(VMConfig::default());
vm.load_program(bytecode);
for ext in extensions {
vm.register_extension(ext.clone());
}
vm.populate_module_objects();
for (name, value) in module_bindings {
let idx = vm
.program
.module_binding_names
.iter()
.position(|n| n == &name);
match idx {
Some(i) => {
let bits = value.slot().raw();
let kind = value.kind();
std::mem::forget(value);
vm.module_binding_write_kinded(i, bits, kind);
}
None => {
drop(value);
}
}
}
let interrupt = Arc::new(AtomicU8::new(0));
vm.set_interrupt(interrupt.clone());
let timeout_interrupt = interrupt.clone();
let _timer_handle = std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_secs(5));
timeout_interrupt.store(1, Ordering::SeqCst);
});
super::comptime_builtins::clear_comptime_directives();
let value = vm.execute(None).map_err(|e| ShapeError::RuntimeError {
message: format!("Comptime handler execution failed: {}", e),
location: None,
})?;
let directives = super::comptime_builtins::take_comptime_directives();
Ok(ComptimeExecutionResult { value, directives })
};
if tokio::runtime::Handle::try_current().is_ok() {
run(module_bindings)
} else {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.map_err(|e| ShapeError::RuntimeError {
message: format!("Failed to create tokio runtime for comptime: {}", e),
location: None,
})?;
rt.block_on(async { run(module_bindings) })
}
}
pub(crate) fn vmvalue_to_literal(value: &KindedSlot) -> shape_ast::ast::Literal {
nb_to_literal(value)
}
pub(crate) fn nb_to_literal(nb: &KindedSlot) -> shape_ast::ast::Literal {
use shape_ast::ast::Literal;
match nb.kind() {
NativeKind::Int64 => return Literal::Int(nb.as_i64().unwrap_or(0)),
NativeKind::Float64 => return Literal::Number(nb.as_f64().unwrap_or(0.0)),
NativeKind::Bool => {
if nb.raw() == 0 {
return Literal::None;
}
return Literal::Bool(nb.as_bool().unwrap_or(false));
}
NativeKind::String => {
if let Some(s) = nb.as_str() {
return Literal::String(s.to_string());
}
return Literal::None;
}
NativeKind::Ptr(HeapKind::Char) => {
if let Some(c) = nb.as_char() {
return Literal::Char(c);
}
return Literal::None;
}
_ => {}
}
let slot_for_hv = nb.slot();
let bits = slot_for_hv.raw();
if bits == 0 {
return Literal::None;
}
let hv = slot_for_hv.as_heap_value();
match hv {
HeapValue::String(s) => Literal::String((**s).clone()),
HeapValue::Decimal(d) => Literal::Decimal(**d),
HeapValue::BigInt(i) => Literal::Int(**i),
HeapValue::Char(c) => Literal::Char(*c),
_ => Literal::String(format!("{}", hv)),
}
}
pub(crate) fn nb_to_expr_public(
nb: &KindedSlot,
span: Span,
) -> std::result::Result<Expr, String> {
nb_to_expr(nb, span)
}
fn nb_to_expr(nb: &KindedSlot, span: Span) -> std::result::Result<Expr, String> {
match nb.kind() {
NativeKind::Int64 => {
return Ok(Expr::Literal(
shape_ast::ast::Literal::Int(nb.as_i64().unwrap_or(0)),
span,
));
}
NativeKind::Float64 => {
return Ok(Expr::Literal(
shape_ast::ast::Literal::Number(nb.as_f64().unwrap_or(0.0)),
span,
));
}
NativeKind::Bool => {
if nb.raw() == 0 {
return Ok(Expr::Literal(shape_ast::ast::Literal::None, span));
}
return Ok(Expr::Literal(
shape_ast::ast::Literal::Bool(nb.as_bool().unwrap_or(false)),
span,
));
}
NativeKind::String => {
if let Some(s) = nb.as_str() {
return Ok(Expr::Literal(
shape_ast::ast::Literal::String(s.to_string()),
span,
));
}
return Ok(Expr::Literal(shape_ast::ast::Literal::None, span));
}
NativeKind::Ptr(HeapKind::Char) => {
if let Some(c) = nb.as_char() {
return Ok(Expr::Literal(shape_ast::ast::Literal::Char(c), span));
}
return Ok(Expr::Literal(shape_ast::ast::Literal::None, span));
}
_ => {}
}
let slot_for_hv = nb.slot();
let bits = slot_for_hv.raw();
if bits == 0 {
return Ok(Expr::Literal(shape_ast::ast::Literal::None, span));
}
let hv = slot_for_hv.as_heap_value();
match hv {
HeapValue::String(s) => Ok(Expr::Literal(
shape_ast::ast::Literal::String((**s).clone()),
span,
)),
HeapValue::Decimal(d) => Ok(Expr::Literal(shape_ast::ast::Literal::Decimal(**d), span)),
HeapValue::BigInt(i) => Ok(Expr::Literal(shape_ast::ast::Literal::Int(**i), span)),
HeapValue::Char(c) => Ok(Expr::Literal(shape_ast::ast::Literal::Char(*c), span)),
HeapValue::TypedObject(storage) => {
let schema_id = storage.schema_id as u32;
let schema = shape_runtime::type_schema::lookup_schema_by_id_public(schema_id)
.ok_or_else(|| {
format!(
"TypedObject schema id {} not found while materializing \
comptime literal — playbook §7 surface, ADR-006 §2.7.4 \
(schema rebind deferred)",
schema_id
)
})?;
let mut entries = Vec::with_capacity(schema.fields.len());
for field_def in schema.fields.iter() {
let idx = field_def.index as usize;
if idx >= storage.slots.len() {
return Err(format!(
"TypedObject slot index {} out of bounds (len={}) — \
schema/storage mismatch",
idx,
storage.slots.len()
));
}
let slot = storage.slots[idx];
let kind = field_kind_for_readback(&field_def.field_type)?;
let kinded_slot = read_typed_object_field(slot, kind, storage.heap_mask, idx);
let value_expr = nb_to_expr(&kinded_slot, span)?;
entries.push(ObjectEntry::Field {
key: field_def.name.clone(),
value: value_expr,
type_annotation: None,
});
}
Ok(Expr::Object(entries, span))
}
other => Err(format!(
"unsupported comptime literal value: HeapValue::{:?}",
other.kind()
)),
}
}
fn field_kind_for_readback(
field_type: &shape_runtime::type_schema::FieldType,
) -> std::result::Result<NativeKind, String> {
field_type.to_native_kind().map_err(|_| {
format!(
"comptime literal: field type {:?} has no kinded projection \
(FieldType::Any cannot be read back without kind metadata — \
ADR-006 §2.7.4 follow-up to land schema rebind / predeclared \
schema kind-narrowing for comptime objects)",
field_type
)
})
}
fn read_typed_object_field(
slot: shape_value::ValueSlot,
kind: NativeKind,
heap_mask: u64,
idx: usize,
) -> KindedSlot {
let is_heap_slot = idx < 64 && (heap_mask >> idx) & 1 == 1;
let bits = slot.raw();
if !is_heap_slot {
return KindedSlot::new(slot, kind);
}
if bits == 0 {
return KindedSlot::none();
}
unsafe {
match kind {
NativeKind::String => {
Arc::increment_strong_count(bits as *const String);
}
NativeKind::Ptr(hk) => match hk {
HeapKind::String => {
Arc::increment_strong_count(bits as *const String);
}
HeapKind::TypedArray => {
let hdr = bits as *const shape_value::v2::heap_header::HeapHeader;
shape_value::v2::refcount::v2_retain(hdr);
}
HeapKind::TypedObject => {
Arc::increment_strong_count(
bits as *const shape_value::TypedObjectStorage,
);
}
HeapKind::Decimal => {
Arc::increment_strong_count(bits as *const rust_decimal::Decimal);
}
HeapKind::BigInt => {
Arc::increment_strong_count(bits as *const i64);
}
_ => {
return KindedSlot::new(slot, kind);
}
},
_ => {}
}
}
KindedSlot::new(slot, kind)
}
#[cfg(test)]
mod tests {
#[test]
#[ignore = "phase-2c — comptime rebuild against typed-Arc HeapValue layout — see ADR-006 §2.4"]
fn placeholder_phase_2c_comptime_tests() {}
use super::execute_comptime;
use shape_ast::ast::{
DestructurePattern, Expr, Literal, Span, Statement, VarKind, VariableDecl,
};
#[test]
fn w17_comptime_arithmetic_sanity() {
let stmts = vec![Statement::Return(
Some(Expr::Literal(Literal::Int(42), Span::DUMMY)),
Span::DUMMY,
)];
let result = execute_comptime(
&stmts,
&[],
&[],
Default::default(),
Default::default(),
Default::default(),
);
assert!(
result.is_ok(),
"comptime arithmetic should still work: {:?}",
result.err()
);
}
#[test]
fn w17_comptime_build_config_dispatches_end_to_end() {
let stmts = vec![Statement::Return(
Some(Expr::FunctionCall {
name: "build_config".to_string(),
args: Vec::new(),
named_args: Vec::new(),
span: Span::DUMMY,
}),
Span::DUMMY,
)];
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
execute_comptime(
&stmts,
&[],
&[],
Default::default(),
Default::default(),
Default::default(),
)
}));
match result {
Ok(Ok(_)) => {
}
Ok(Err(e)) => {
let msg = format!("{:?}", e);
assert!(
!msg.contains("populate_module_objects") && !msg.contains("NotImplemented"),
"dispatch path should not surface populate_module_objects \
NotImplemented (W17 close gate): {}",
msg
);
}
Err(_) => {
}
}
}
#[test]
fn w17_comptime_implements_dispatches_end_to_end() {
let stmts = vec![Statement::Return(
Some(Expr::FunctionCall {
name: "implements".to_string(),
args: vec![
Expr::Literal(Literal::String("int".to_string()), Span::DUMMY),
Expr::Literal(Literal::String("Add".to_string()), Span::DUMMY),
],
named_args: Vec::new(),
span: Span::DUMMY,
}),
Span::DUMMY,
)];
let result = execute_comptime(
&stmts,
&[],
&[],
Default::default(),
Default::default(),
Default::default(),
);
assert!(
result.is_ok(),
"implements() should dispatch end-to-end: {:?}",
result.err()
);
}
#[test]
fn w17_comptime_warning_dispatches_end_to_end() {
let stmts = vec![Statement::Expression(
Expr::FunctionCall {
name: "warning".to_string(),
args: vec![Expr::Literal(
Literal::String("W17 test warning".to_string()),
Span::DUMMY,
)],
named_args: Vec::new(),
span: Span::DUMMY,
},
Span::DUMMY,
)];
let result = execute_comptime(
&stmts,
&[],
&[],
Default::default(),
Default::default(),
Default::default(),
);
assert!(
result.is_ok(),
"warning() should dispatch end-to-end: {:?}",
result.err()
);
}
#[test]
fn w17_comptime_error_dispatches_end_to_end() {
let stmts = vec![Statement::Expression(
Expr::FunctionCall {
name: "error".to_string(),
args: vec![Expr::Literal(
Literal::String("W17 test error".to_string()),
Span::DUMMY,
)],
named_args: Vec::new(),
span: Span::DUMMY,
},
Span::DUMMY,
)];
let result = execute_comptime(
&stmts,
&[],
&[],
Default::default(),
Default::default(),
Default::default(),
);
assert!(
result.is_err(),
"error() should abort comptime execution: {:?}",
result.ok().map(|r| r.value)
);
let err_msg = format!("{:?}", result.err().unwrap());
assert!(
err_msg.contains("[comptime error]") || err_msg.contains("W17 test error"),
"error message should surface the comptime-error path: {}",
err_msg
);
}
#[test]
fn w7_type_info_comptime_only_contract() {
let code = r#"let x = type_info("Point")"#;
let program = shape_ast::parser::parse_program(code).expect("parse");
let result = crate::compiler::BytecodeCompiler::new().compile(&program);
assert!(
result.is_err(),
"type_info() outside comptime should fail (comptime-only gate)"
);
let err_msg = format!("{}", result.unwrap_err());
assert!(
err_msg.contains("comptime-only builtin")
|| err_msg.contains("comptime { }"),
"Error should surface the comptime-only-builtin gate (W7): {}",
err_msg
);
}
use shape_ast::ast::TypeAnnotation as TypeAnn;
fn assert_dispatch_reached(
stmts: Vec<Statement>,
trait_impl_keys: std::collections::HashSet<String>,
snapshot: crate::compiler::comptime_builtins::TypeReflectionSnapshot,
ctx: &str,
) {
let known_types: std::collections::HashSet<String> = snapshot
.struct_defs
.keys()
.chain(snapshot.alias_defs.keys())
.chain(snapshot.enum_defs.keys())
.cloned()
.collect();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
execute_comptime(&stmts, &[], &[], trait_impl_keys, known_types, snapshot)
}));
match result {
Ok(Ok(_)) => {}
Ok(Err(e)) => {
let msg = format!("{:?}", e);
assert!(
!msg.contains("populate_module_objects")
&& !msg.contains("NotImplemented"),
"{ctx}: dispatch chain must not surface the pre-§2.7.26 \
NotImplemented stub: {msg}",
);
assert!(
!msg.contains("type_info has been removed"),
"{ctx}: must not surface the retired \
`type_info has been removed` legacy gate: {msg}",
);
}
Err(_) => {
}
}
}
fn snapshot_with_struct(name: &str, fields: &[(&str, TypeAnn)]) -> crate::compiler::comptime_builtins::TypeReflectionSnapshot {
let mut snapshot = crate::compiler::comptime_builtins::TypeReflectionSnapshot::default();
let ordered: Vec<(String, TypeAnn)> = fields
.iter()
.map(|(n, t)| (n.to_string(), t.clone()))
.collect();
snapshot.struct_defs.insert(name.to_string(), ordered);
snapshot
}
fn snapshot_with_enum(name: &str, variants: &[&str]) -> crate::compiler::comptime_builtins::TypeReflectionSnapshot {
let mut snapshot = crate::compiler::comptime_builtins::TypeReflectionSnapshot::default();
snapshot.enum_defs.insert(
name.to_string(),
variants.iter().map(|v| v.to_string()).collect(),
);
snapshot
}
#[test]
fn w14_2_c1_chained_kind_access_on_struct() {
let stmts = vec![Statement::Return(
Some(Expr::PropertyAccess {
object: Box::new(Expr::FunctionCall {
name: "type_info".to_string(),
args: vec![Expr::Literal(
Literal::String("Point".to_string()),
Span::DUMMY,
)],
named_args: Vec::new(),
span: Span::DUMMY,
}),
property: "kind".to_string(),
optional: false,
span: Span::DUMMY,
}),
Span::DUMMY,
)];
let snapshot = snapshot_with_struct(
"Point",
&[
("x", TypeAnn::Basic("int".to_string())),
("y", TypeAnn::Basic("int".to_string())),
],
);
assert_dispatch_reached(
stmts,
Default::default(),
snapshot,
"w14_2_c1_chained_kind_access_on_struct",
);
}
#[test]
fn w14_2_c1_chained_name_access_on_struct() {
let stmts = vec![Statement::Return(
Some(Expr::PropertyAccess {
object: Box::new(Expr::FunctionCall {
name: "type_info".to_string(),
args: vec![Expr::Literal(
Literal::String("Point".to_string()),
Span::DUMMY,
)],
named_args: Vec::new(),
span: Span::DUMMY,
}),
property: "name".to_string(),
optional: false,
span: Span::DUMMY,
}),
Span::DUMMY,
)];
let snapshot = snapshot_with_struct(
"Point",
&[("x", TypeAnn::Basic("int".to_string()))],
);
assert_dispatch_reached(
stmts,
Default::default(),
snapshot,
"w14_2_c1_chained_name_access_on_struct",
);
}
#[test]
fn w14_2_c1_chained_bind_then_access() {
let stmts = vec![
Statement::VariableDecl(
VariableDecl {
kind: VarKind::Let,
is_mut: false,
pattern: DestructurePattern::Identifier("info".to_string(), Span::DUMMY),
type_annotation: None,
value: Some(Expr::FunctionCall {
name: "type_info".to_string(),
args: vec![Expr::Literal(
Literal::String("Point".to_string()),
Span::DUMMY,
)],
named_args: Vec::new(),
span: Span::DUMMY,
}),
ownership: Default::default(),
},
Span::DUMMY,
),
Statement::Return(
Some(Expr::PropertyAccess {
object: Box::new(Expr::Identifier("info".to_string(), Span::DUMMY)),
property: "kind".to_string(),
optional: false,
span: Span::DUMMY,
}),
Span::DUMMY,
),
];
let snapshot = snapshot_with_struct(
"Point",
&[("x", TypeAnn::Basic("int".to_string()))],
);
assert_dispatch_reached(
stmts,
Default::default(),
snapshot,
"w14_2_c1_chained_bind_then_access",
);
}
#[test]
fn w14_2_c1_build_config_and_type_info_in_same_block() {
let stmts = vec![
Statement::VariableDecl(
VariableDecl {
kind: VarKind::Let,
is_mut: false,
pattern: DestructurePattern::Identifier("cfg".to_string(), Span::DUMMY),
type_annotation: None,
value: Some(Expr::FunctionCall {
name: "build_config".to_string(),
args: Vec::new(),
named_args: Vec::new(),
span: Span::DUMMY,
}),
ownership: Default::default(),
},
Span::DUMMY,
),
Statement::Return(
Some(Expr::FunctionCall {
name: "type_info".to_string(),
args: vec![Expr::Literal(
Literal::String("Point".to_string()),
Span::DUMMY,
)],
named_args: Vec::new(),
span: Span::DUMMY,
}),
Span::DUMMY,
),
];
let snapshot = snapshot_with_struct(
"Point",
&[("x", TypeAnn::Basic("int".to_string()))],
);
assert_dispatch_reached(
stmts,
Default::default(),
snapshot,
"w14_2_c1_build_config_and_type_info_in_same_block",
);
}
#[test]
fn w14_2_c1_chained_access_on_both_builtins() {
let stmts = vec![
Statement::VariableDecl(
VariableDecl {
kind: VarKind::Let,
is_mut: false,
pattern: DestructurePattern::Identifier("arch".to_string(), Span::DUMMY),
type_annotation: None,
value: Some(Expr::PropertyAccess {
object: Box::new(Expr::FunctionCall {
name: "build_config".to_string(),
args: Vec::new(),
named_args: Vec::new(),
span: Span::DUMMY,
}),
property: "target_arch".to_string(),
optional: false,
span: Span::DUMMY,
}),
ownership: Default::default(),
},
Span::DUMMY,
),
Statement::Return(
Some(Expr::PropertyAccess {
object: Box::new(Expr::FunctionCall {
name: "type_info".to_string(),
args: vec![Expr::Literal(
Literal::String("Point".to_string()),
Span::DUMMY,
)],
named_args: Vec::new(),
span: Span::DUMMY,
}),
property: "kind".to_string(),
optional: false,
span: Span::DUMMY,
}),
Span::DUMMY,
),
];
let snapshot = snapshot_with_struct(
"Point",
&[("x", TypeAnn::Basic("int".to_string()))],
);
assert_dispatch_reached(
stmts,
Default::default(),
snapshot,
"w14_2_c1_chained_access_on_both_builtins",
);
}
#[test]
fn w14_2_c1_type_info_on_array_generic() {
let stmts = vec![Statement::Return(
Some(Expr::FunctionCall {
name: "type_info".to_string(),
args: vec![Expr::Literal(
Literal::String("Array<int>".to_string()),
Span::DUMMY,
)],
named_args: Vec::new(),
span: Span::DUMMY,
}),
Span::DUMMY,
)];
assert_dispatch_reached(
stmts,
Default::default(),
Default::default(),
"w14_2_c1_type_info_on_array_generic",
);
}
#[test]
fn w14_2_c1_type_info_on_option_of_struct() {
let stmts = vec![Statement::Return(
Some(Expr::FunctionCall {
name: "type_info".to_string(),
args: vec![Expr::Literal(
Literal::String("Option<Point>".to_string()),
Span::DUMMY,
)],
named_args: Vec::new(),
span: Span::DUMMY,
}),
Span::DUMMY,
)];
let snapshot = snapshot_with_struct(
"Point",
&[("x", TypeAnn::Basic("int".to_string()))],
);
assert_dispatch_reached(
stmts,
Default::default(),
snapshot,
"w14_2_c1_type_info_on_option_of_struct",
);
}
#[test]
fn w14_2_c1_type_info_on_result_two_params() {
let stmts = vec![Statement::Return(
Some(Expr::FunctionCall {
name: "type_info".to_string(),
args: vec![Expr::Literal(
Literal::String("Result<int, string>".to_string()),
Span::DUMMY,
)],
named_args: Vec::new(),
span: Span::DUMMY,
}),
Span::DUMMY,
)];
assert_dispatch_reached(
stmts,
Default::default(),
Default::default(),
"w14_2_c1_type_info_on_result_two_params",
);
}
#[test]
fn w14_2_c1_chained_kind_on_hashmap_generic() {
let stmts = vec![Statement::Return(
Some(Expr::PropertyAccess {
object: Box::new(Expr::FunctionCall {
name: "type_info".to_string(),
args: vec![Expr::Literal(
Literal::String("HashMap<string, int>".to_string()),
Span::DUMMY,
)],
named_args: Vec::new(),
span: Span::DUMMY,
}),
property: "kind".to_string(),
optional: false,
span: Span::DUMMY,
}),
Span::DUMMY,
)];
assert_dispatch_reached(
stmts,
Default::default(),
Default::default(),
"w14_2_c1_chained_kind_on_hashmap_generic",
);
}
#[test]
fn w14_2_c1_type_info_on_registered_enum() {
let stmts = vec![Statement::Return(
Some(Expr::FunctionCall {
name: "type_info".to_string(),
args: vec![Expr::Literal(
Literal::String("Color".to_string()),
Span::DUMMY,
)],
named_args: Vec::new(),
span: Span::DUMMY,
}),
Span::DUMMY,
)];
let snapshot = snapshot_with_enum("Color", &["Red", "Green", "Blue"]);
assert_dispatch_reached(
stmts,
Default::default(),
snapshot,
"w14_2_c1_type_info_on_registered_enum",
);
}
#[test]
fn w14_2_c1_chained_kind_on_registered_enum() {
let stmts = vec![Statement::Return(
Some(Expr::PropertyAccess {
object: Box::new(Expr::FunctionCall {
name: "type_info".to_string(),
args: vec![Expr::Literal(
Literal::String("Color".to_string()),
Span::DUMMY,
)],
named_args: Vec::new(),
span: Span::DUMMY,
}),
property: "kind".to_string(),
optional: false,
span: Span::DUMMY,
}),
Span::DUMMY,
)];
let snapshot = snapshot_with_enum("Color", &["Red", "Green", "Blue"]);
assert_dispatch_reached(
stmts,
Default::default(),
snapshot,
"w14_2_c1_chained_kind_on_registered_enum",
);
}
#[test]
fn w14_2_c1_type_info_on_undefined_type_returns_unknown() {
let stmts = vec![Statement::Return(
Some(Expr::FunctionCall {
name: "type_info".to_string(),
args: vec![Expr::Literal(
Literal::String("UndefinedXYZ".to_string()),
Span::DUMMY,
)],
named_args: Vec::new(),
span: Span::DUMMY,
}),
Span::DUMMY,
)];
assert_dispatch_reached(
stmts,
Default::default(),
Default::default(),
"w14_2_c1_type_info_on_undefined_type_returns_unknown",
);
}
#[test]
fn w14_2_c1_type_info_bare_ident_rewrites_for_unknown() {
let stmts = vec![Statement::Return(
Some(Expr::FunctionCall {
name: "type_info".to_string(),
args: vec![Expr::Identifier("UndefinedXYZ".to_string(), Span::DUMMY)],
named_args: Vec::new(),
span: Span::DUMMY,
}),
Span::DUMMY,
)];
assert_dispatch_reached(
stmts,
Default::default(),
Default::default(),
"w14_2_c1_type_info_bare_ident_rewrites_for_unknown",
);
}
#[test]
fn w14_2_c1_chained_type_info_source_level_parse_and_gates() {
let code = r#"
type Point {
x: int,
y: int
}
const KIND = comptime {
type_info(Point).kind
}
"#;
let program = shape_ast::parser::parse_program(code);
assert!(
program.is_ok(),
"W14.2-C1: chained `type_info(Point).kind` must parse: {:?}",
program.err()
);
let result =
crate::compiler::BytecodeCompiler::new().compile(&program.unwrap());
if let Err(e) = result {
let msg = format!("{}", e);
assert!(
!msg.contains("type_info has been removed"),
"W14.2-C1: chained type_info().kind must not surface the \
retired `type_info has been removed` gate: {msg}",
);
assert!(
!msg.contains("comptime-only builtin"),
"W14.2-C1: type_info inside a comptime block must not \
trigger the comptime-only-builtin gate: {msg}",
);
}
}
#[test]
fn w14_2_c1_build_config_plus_type_info_source_level_parse_and_gates() {
let code = r#"
type Point {
x: int
}
const COMBO = comptime {
let cfg = build_config()
let info = type_info(Point)
info.name
}
"#;
let program = shape_ast::parser::parse_program(code);
assert!(
program.is_ok(),
"W14.2-C1: build_config + type_info combo must parse: {:?}",
program.err()
);
let result =
crate::compiler::BytecodeCompiler::new().compile(&program.unwrap());
if let Err(e) = result {
let msg = format!("{}", e);
assert!(
!msg.contains("type_info has been removed"),
"W14.2-C1: must not surface retired legacy gate: {msg}",
);
assert!(
!msg.contains("comptime-only builtin"),
"W14.2-C1: builtins inside comptime block must not gate: {msg}",
);
}
}
#[test]
fn w14_2_c1_type_info_on_generic_string_source_level_parse() {
let code = r#"
const INFO = comptime {
type_info("Array<int>").kind
}
"#;
let program = shape_ast::parser::parse_program(code);
assert!(
program.is_ok(),
"W14.2-C1: generic-string `type_info(\"Array<int>\").kind` must \
parse: {:?}",
program.err()
);
}
#[test]
fn w14_2_c1_chained_multi_level_property_parses() {
let code = r#"
const X = comptime {
type_info("Point").name.length
}
"#;
let program = shape_ast::parser::parse_program(code);
assert!(
program.is_ok(),
"W14.2-C1: multi-level chained property access on type_info() \
must parse (future-proofing for recursive FieldInfo): {:?}",
program.err()
);
}
}
#[cfg(any())]
#[cfg(test)]
mod tests_deferred {
use super::*;
use shape_ast::ast::{BinaryOp, Expr, Literal, Span, Statement};
use shape_runtime::typed_module_exports::register_test_function;
use shape_value::heap_value::HeapValue;
#[test]
fn test_comptime_simple_return() {
let stmts = vec![Statement::Return(
Some(Expr::Literal(Literal::Int(42), Span::DUMMY)),
Span::DUMMY,
)];
let result = execute_comptime(&stmts, &[], &[], Default::default(), Default::default(), Default::default());
assert!(
result.is_ok(),
"Comptime should succeed: {:?}",
result.err()
);
assert_eq!(result.unwrap().value, ValueWord::from_i64(42));
}
#[test]
fn test_comptime_string_return() {
let stmts = vec![Statement::Return(
Some(Expr::Literal(
Literal::String("hello".to_string()),
Span::DUMMY,
)),
Span::DUMMY,
)];
let result = execute_comptime(&stmts, &[], &[], Default::default(), Default::default(), Default::default());
assert!(
result.is_ok(),
"Comptime should succeed: {:?}",
result.err()
);
let val = result.unwrap().value;
assert_eq!(
val.as_arc_string().expect("Expected String").as_ref() as &str,
"hello"
);
}
#[test]
fn test_comptime_arithmetic() {
let stmts = vec![Statement::Return(
Some(Expr::BinaryOp {
left: Box::new(Expr::Literal(Literal::Int(2), Span::DUMMY)),
op: BinaryOp::Add,
right: Box::new(Expr::Literal(Literal::Int(3), Span::DUMMY)),
span: Span::DUMMY,
}),
Span::DUMMY,
)];
let result = execute_comptime(&stmts, &[], &[], Default::default(), Default::default(), Default::default());
assert!(
result.is_ok(),
"Comptime arithmetic should succeed: {:?}",
result.err()
);
assert_eq!(
result
.unwrap()
.value
.as_number_coerce()
.expect("Expected 5"),
5.0
);
}
#[test]
fn test_comptime_with_sync_extension() {
use shape_runtime::module_exports::ModuleExports;
let mut ext = ModuleExports::new("mock_db");
register_test_function(&mut ext,
"get_schema",
|_args, _ctx: &shape_runtime::module_exports::ModuleContext| {
Ok(ValueWord::from_string(Arc::new(
"id:int,name:string".to_string(),
)))
},
);
let code = r#"
use mock_db
mock_db::get_schema()
"#;
let program = shape_ast::parser::parse_program(code).expect("parse");
let mut compiler = BytecodeCompiler::new();
compiler.extension_registry = Some(Arc::new(vec![ext.clone()]));
let bytecode = compiler.compile(&program).expect("compile");
let mut vm = VirtualMachine::new(VMConfig::default());
vm.load_program(bytecode);
vm.register_extension(ext);
vm.populate_module_objects();
let result = vm.execute(None);
assert!(
result.is_ok(),
"Extension call should succeed: {:?}",
result.err()
);
let val = result.unwrap().clone();
assert_eq!(
val.as_arc_string()
.expect("Expected schema string")
.as_ref() as &str,
"id:int,name:string"
);
}
#[test]
fn test_comptime_extension_registry_flows_through_compiler() {
use shape_runtime::module_exports::ModuleExports;
let mut ext = ModuleExports::new("test_ext");
register_test_function(&mut ext,
"version",
|_args, _ctx: &shape_runtime::module_exports::ModuleContext| {
Ok(ValueWord::from_string(Arc::new("1.0".to_string())))
},
);
let mut compiler = BytecodeCompiler::new();
compiler.extension_registry = Some(Arc::new(vec![ext]));
assert!(compiler.extension_registry.is_some());
assert_eq!(compiler.extension_registry.as_ref().unwrap().len(), 1);
assert_eq!(
compiler.extension_registry.as_ref().unwrap()[0].name,
"test_ext"
);
}
#[test]
fn test_vmvalue_to_literal_int() {
let lit = vmvalue_to_literal(&ValueWord::from_i64(42));
assert_eq!(lit, Literal::Int(42));
}
#[test]
fn test_vmvalue_to_literal_number() {
let lit = vmvalue_to_literal(&ValueWord::from_f64(3.14));
assert_eq!(lit, Literal::Number(3.14));
}
#[test]
fn test_vmvalue_to_literal_string() {
let lit = vmvalue_to_literal(&ValueWord::from_string(Arc::new("hello".to_string())));
assert_eq!(lit, Literal::String("hello".to_string()));
}
#[test]
fn test_vmvalue_to_literal_bool() {
let lit = vmvalue_to_literal(&ValueWord::from_bool(true));
assert_eq!(lit, Literal::Bool(true));
}
#[test]
fn test_vmvalue_to_literal_none() {
let lit = vmvalue_to_literal(&ValueWord::none());
assert_eq!(lit, Literal::None);
}
#[test]
fn test_vmvalue_to_literal_unit() {
let lit = vmvalue_to_literal(&ValueWord::unit());
assert_eq!(lit, Literal::Unit);
}
#[test]
fn test_comptime_block_parsed_and_executed() {
let stmts = vec![Statement::Return(
Some(Expr::BinaryOp {
left: Box::new(Expr::Literal(Literal::Int(10), Span::DUMMY)),
op: BinaryOp::Mul,
right: Box::new(Expr::Literal(Literal::Int(5), Span::DUMMY)),
span: Span::DUMMY,
}),
Span::DUMMY,
)];
let result = execute_comptime(&stmts, &[], &[], Default::default(), Default::default(), Default::default());
assert!(
result.is_ok(),
"Comptime multiplication should succeed: {:?}",
result.err()
);
assert_eq!(
result
.unwrap()
.value
.as_number_coerce()
.expect("Expected 50"),
50.0
);
}
#[test]
fn test_comptime_builtins_available_in_comptime_block() {
let stmts = vec![Statement::Return(
Some(Expr::FunctionCall {
name: "build_config".to_string(),
args: Vec::new(),
named_args: Vec::new(),
span: Span::DUMMY,
}),
Span::DUMMY,
)];
let result = execute_comptime(&stmts, &[], &[], Default::default(), Default::default(), Default::default())
.map(|r| r.value);
assert!(
result.is_ok(),
"build_config() should work in comptime: {:?}",
result.err()
);
let val = result.unwrap();
let is_typed_object_or_string = val
.as_heap_ref() .is_some_and(|h| matches!(h, HeapValue::TypedObject { .. } | HeapValue::String(_)));
assert!(
is_typed_object_or_string,
"Expected TypedObject or String, got {:?}",
val,
);
}
#[test]
fn test_comptime_print_build_config_no_stack_overflow() {
let stmts = vec![Statement::Expression(
Expr::FunctionCall {
name: "print".to_string(),
args: vec![Expr::FunctionCall {
name: "build_config".to_string(),
args: Vec::new(),
named_args: Vec::new(),
span: Span::DUMMY,
}],
named_args: Vec::new(),
span: Span::DUMMY,
},
Span::DUMMY,
)];
let result = execute_comptime(&stmts, &[], &[], Default::default(), Default::default(), Default::default());
assert!(
result.is_ok(),
"print(build_config()) should execute in comptime: {:?}",
result.err()
);
}
#[test]
fn test_comptime_only_builtins_rejected_outside_comptime() {
let code = r#"let x = type_info("Point")"#;
let program = shape_ast::parser::parse_program(code).expect("parse");
let result = BytecodeCompiler::new().compile(&program);
assert!(result.is_err(), "type_info() outside comptime should fail");
let err_msg = format!("{}", result.unwrap_err());
assert!(
err_msg.contains("type_info has been removed"),
"Error should mention removal: {}",
err_msg
);
let code2 = r#"let y = build_config()"#;
let program2 = shape_ast::parser::parse_program(code2).expect("parse");
let result2 = BytecodeCompiler::new().compile(&program2);
assert!(
result2.is_err(),
"build_config() outside comptime should fail"
);
}
#[test]
fn test_comptime_with_target_simple() {
let handler_body = Expr::PropertyAccess {
object: Box::new(Expr::Identifier("target".to_string(), Span::DUMMY)),
property: "name".to_string(),
optional: false,
span: Span::DUMMY,
};
let target_value = shape_runtime::type_schema::typed_object_from_pairs(&[
(
"kind",
ValueWord::from_string(Arc::new("function".to_string())),
),
(
"name",
ValueWord::from_string(Arc::new("my_func".to_string())),
),
("fields", ValueWord::from_array(shape_value::vmarray_from_vec(vec![]))),
("params", ValueWord::from_array(shape_value::vmarray_from_vec(vec![]))),
("return_type", ValueWord::none()),
("annotations", ValueWord::from_array(shape_value::vmarray_from_vec(vec![]))),
("captures", ValueWord::from_array(shape_value::vmarray_from_vec(vec![]))),
]);
let result = execute_comptime_with_target(
&handler_body,
"target",
target_value,
&[],
Default::default(),
Default::default(),
);
assert!(
result.is_ok(),
"Comptime with target should succeed: {:?}",
result.err()
);
let val = result.unwrap().value;
assert_eq!(
val.as_arc_string()
.expect("Expected String(\"my_func\")")
.as_ref() as &str,
"my_func"
);
}
#[test]
fn test_comptime_with_target_from_function() {
use crate::compiler::comptime_target::ComptimeTarget;
use shape_ast::ast::{DestructurePattern, FunctionParameter, TypeAnnotation};
let func = FunctionDef {
name: "greet".to_string(),
name_span: Span::DUMMY,
declaring_module_path: None,
doc_comment: None,
params: vec![FunctionParameter {
pattern: DestructurePattern::Identifier("name".to_string(), Span::DUMMY),
is_const: false,
is_reference: false,
is_mut_reference: false,
is_out: false,
type_annotation: Some(TypeAnnotation::Basic("string".to_string())),
default_value: None,
}],
return_type: Some(TypeAnnotation::Basic("string".to_string())),
body: Vec::new(),
type_params: None,
annotations: Vec::new(),
is_async: false,
is_comptime: false,
where_clause: None,
};
let target = ComptimeTarget::from_function(&func);
let target_value = target.to_nanboxed();
let handler_body = Expr::PropertyAccess {
object: Box::new(Expr::Identifier("t".to_string(), Span::DUMMY)),
property: "kind".to_string(),
optional: false,
span: Span::DUMMY,
};
let result = execute_comptime_with_target(
&handler_body,
"t",
target_value,
&[],
Default::default(),
Default::default(),
);
assert!(
result.is_ok(),
"Comptime with function target should succeed: {:?}",
result.err()
);
let val = result.unwrap().value;
assert_eq!(
val.as_arc_string()
.expect("Expected String(\"function\")")
.as_ref() as &str,
"function"
);
}
#[test]
fn test_comptime_handler_end_to_end() {
let code = r#"
annotation inspect() {
comptime post(target, ctx) {
target.name
}
}
@inspect()
function greet(name) {
return "hello " + name
}
greet("world")
"#;
let program = shape_ast::parser::parse_program(code).expect("parse");
let result = BytecodeCompiler::new().compile(&program);
assert!(
result.is_ok(),
"Comptime handler end-to-end should compile: {:?}",
result.err()
);
let bytecode = result.unwrap();
let mut vm = VirtualMachine::new(VMConfig::default());
vm.load_program(bytecode);
let exec_result = vm.execute(None);
assert!(
exec_result.is_ok(),
"Execution should succeed: {:?}",
exec_result.err()
);
let val = exec_result.unwrap().clone();
assert_eq!(
val.as_arc_string()
.expect("Expected String(\"hello world\")")
.as_ref() as &str,
"hello world"
);
}
#[test]
fn test_comptime_handler_accesses_target_params() {
let code = r#"
annotation check_params() {
comptime post(target, ctx) {
target.params
}
}
@check_params()
function add(x, y) {
return x + y
}
add(1, 2)
"#;
let program = shape_ast::parser::parse_program(code).expect("parse");
let result = BytecodeCompiler::new().compile(&program);
assert!(
result.is_ok(),
"Comptime handler with params access should compile: {:?}",
result.err()
);
let bytecode = result.unwrap();
let mut vm = VirtualMachine::new(VMConfig::default());
vm.load_program(bytecode);
let exec_result = vm.execute(None);
assert!(
exec_result.is_ok(),
"Should execute: {:?}",
exec_result.err()
);
assert_eq!(
exec_result
.unwrap()
.clone()
.as_number_coerce()
.expect("Expected 3"),
3.0
);
}
#[test]
fn test_comptime_fn_not_compiled_into_runtime_bytecode() {
let code = r#"
comptime fn helper() {
42
}
comptime {
helper()
}
100
"#;
let program = shape_ast::parser::parse_program(code).expect("parse");
let bytecode = BytecodeCompiler::new().compile(&program).expect("compile");
let helper_func = bytecode.functions.iter().find(|f| f.name == "helper");
if let Some(func) = helper_func {
assert_eq!(
func.body_length, 0,
"comptime fn should not have compiled body in runtime bytecode"
);
}
let mut vm = VirtualMachine::new(VMConfig::default());
vm.load_program(bytecode);
let result = vm.execute(None).expect("execute");
assert_eq!(result.as_number_coerce().expect("Expected 100"), 100.0);
}
#[test]
fn test_comptime_fn_not_callable_at_runtime() {
let code = r#"
comptime fn secret() {
42
}
secret()
"#;
let program = shape_ast::parser::parse_program(code).expect("parse");
let result = BytecodeCompiler::new().compile(&program);
assert!(
result.is_err(),
"Calling comptime fn at runtime should fail"
);
let err_msg = format!("{}", result.unwrap_err());
assert!(
err_msg.contains("comptime"),
"Error should mention comptime: {}",
err_msg
);
}
}