Files
endorphin-aster-vm/crates/compiler/tests/classes.rs
T

481 lines
21 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Rust 测试框架驱动的源码类集成测试:从前端/编译器到 VM 的宿主构造、绑定方法、字段检查及 GC。
//! execute/try_execute 按现有 globals 声明可用宿主名,加载临时代码,执行后统一归还代码根。
use compiler::{CompileOptions, compile_source};
use endor_core::{
ContextNativeFunction, EndorTypeObject, NativeCallable, NativeContext, NativeResult,
ObjectPayload, TypeConstructor, VmError,
};
use endor_core::{ObjectRef, Value, Vm};
use frontend::{EndorReader, ParseMode};
#[derive(Debug)]
/// 原生实例载荷,WidgetFactory 分配;字符串是宿主标签,没有需要 trace 的托管对象边。
struct NativeWidget(String);
impl ObjectPayload for NativeWidget {
/// Store::payload 的类型检查通过 Any 下转,测试据此读取原生标签。
fn as_any(&self) -> &dyn std::any::Any {
self
}
/// 对象种类诊断入口使用稳定标签 widget,与测试载荷检查保持一致。
fn kind_name(&self) -> &'static str {
"widget"
}
/// Store 属性协议查询此开关;允许在原生实例上存放源码类声明的动态字段。
fn allows_attributes(&self) -> bool {
true
}
}
#[derive(Debug)]
/// NativeTyped 构造策略使用的宿主回调;源码子类沿继承链复用它,但实际实例附着请求的子类。
struct WidgetFactory;
impl NativeCallable for WidgetFactory {
/// VM 原生调用入口调用;参数依次为请求的 Type、宿主标签、声明字段值,结果由 NativeContext 返回协议接管。
fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result<NativeResult, VmError> {
// 解构 NativeTyped 调用布局;类型对象与标签必须存在,其余切片作为字段参数。
let [
Value::Object(requested),
Value::Text(native_label),
values @ ..,
] = args
else {
return Err(VmError::InvalidArgument("widget constructor arguments"));
};
// 复制请求类型的完整字段元数据,以便稍后可变访问 Store 并分配实例。
let fields = cx
.store()
.payload::<EndorTypeObject>(*requested, "Type")?
.fields()
.to_vec();
// 字段数量必须匹配,错误时还没有分配新实例。
if values.len() != fields.len() {
return Err(VmError::InvalidArgument("widget field count"));
}
// 先校验字段注解,避免无效实参进入实例分配和字段写入阶段。
cx.store().check_class_field_arguments(*requested, values)?;
// 通过当前原生上下文分配载荷;发生后续错误时由上下文清理临时分配根。
let instance = cx.allocate(NativeWidget(native_label.clone()))?;
// 附着实际请求的 Type,而非固定 Widget,保持 Child/Grand 的名义身份。
cx.attach_type(instance, *requested)?;
// 按声明字段顺序写值,Value clone 仍由 Store 属性更新建立内部引用边。
for (field, value) in fields.iter().zip(values) {
cx.set_attr(instance, &field.name, value.clone())?;
}
Ok(NativeResult::Return(Value::Object(instance)))
}
}
#[test]
/// 由 Rust 测试框架调用:验证源码两级继承仍由原生工厂分配载荷,字段约束和名义父类型保留,结果根独立保活类型图。
fn source_subclasses_preserve_native_allocation_fields_and_nominal_identity() {
// 隔离本场景堆、类型配置与执行状态,最后检查没有遗留对象。
let mut vm = Vm::new().unwrap();
// 开启源码注解及字段检查,下面的类型错误才会按约束拒绝。
vm.set_source_type_checks(true).unwrap();
// 测试持环境初始根,源码函数和类型声明可在内部引用此环境。
let globals = vm.new_dict().unwrap();
// 引导得到 Object/Type 拥有根,注册环境后分别归还测试根。
let roots = vm.bootstrap_types().unwrap();
// 注册 NativeContext 回调,后续通过 NativeTyped 类型构造协议调用。
let factory = vm
.allocate(ContextNativeFunction::new("widget", WidgetFactory, vec![]))
.unwrap();
// 创建宿主 Widget 类型,以 Object 为父类并保存原生构造器引用。
let widget = vm
.new_type(
"Widget",
roots.object,
TypeConstructor::NativeTyped(factory),
)
.unwrap();
// 把引导类型/宿主类型放入源码可访问的环境,再归还各自外部根。
for (name, object) in [
("Object", roots.object),
("Type", roots.type_type),
("Widget", widget),
] {
vm.dict_set(globals, Value::Text(name.into()), Value::Object(object))
.unwrap();
vm.release(object).unwrap();
}
// Widget 已持有工厂内部边,测试不再额外持工厂根。
vm.release(factory).unwrap();
// Child 增加数值字段,Grand 再增加文本字段,两层源码继承都沿用 Widget 原生分配策略。
let source = "class Child: Widget { value: Number }\nclass Grand: Child { label: String }";
assert_eq!(
execute(&mut vm, globals, source, ParseMode::Module),
Value::Nil
);
// 检查确实由运行时字段类型约束拒绝,而非返回一个看似成功的实例。
assert!(matches!(
try_execute(
&mut vm,
globals,
r#"Grand("native", "bad", "extra")"#,
ParseMode::Expression
),
Err(VmError::TypeMismatch { .. })
));
// 实际调用 Grand 得到拥有实例结果,随后独立于 globals 验证其原生载荷及继承字段。
let value = execute(
&mut vm,
globals,
"Grand(\"native\", 9, \"extra\")",
ParseMode::Expression,
);
// 成功结果必须是托管实例;此句柄持有 execute 返回的拥有根。
let Value::Object(instance) = value else {
panic!("native instance")
};
assert_eq!(
vm.payload::<NativeWidget>(instance, "widget").unwrap().0,
"native"
);
assert!(vm.is_instance(instance, widget).unwrap());
assert_eq!(
vm.get_attr(instance, "value").unwrap(),
Value::Number(9_i64.into())
);
assert_eq!(
vm.get_attr(instance, "label").unwrap(),
Value::Text("extra".into())
);
// 先清环境的全局拥有边,打断函数/类型与环境之间可能的引用环。
vm.dict_clear(globals).unwrap();
// 归还环境外部根,剩余图是否存活只由其他拥有根决定。
vm.release(globals).unwrap();
// 收集已无外部根的类型/函数/环境环;仍由实例或回调拥有的图必须保留。
vm.collect_cycles();
assert!(vm.is_instance(instance, widget).unwrap());
// 归还实例拥有结果根,之后其类型链及内部图也应可清理。
vm.release(instance).unwrap();
// 收集已无外部根的类型/函数/环境环;仍由实例或回调拥有的图必须保留。
vm.collect_cycles();
assert_eq!(vm.object_count(), 0);
}
/// 成功路径测试复用的包装,try_execute 的 VM 错误直接使该断言场景失败;返回 Value 归调用方释放。
fn execute(vm: &mut Vm, globals: ObjectRef, source: &str, mode: ParseMode) -> Value {
try_execute(vm, globals, source, mode).unwrap()
}
/// 类测试共用的编译/执行桥接,解析与加载失败直接使测试失败,VM 执行错误则原样返回给错误用例。
/// globals 是借用环境,返回对象值有独立拥有根;无论执行成功或失败,本辅助函数都归还加载的代码根。
fn try_execute(
vm: &mut Vm,
globals: ObjectRef,
source: &str,
mode: ParseMode,
) -> Result<Value, VmError> {
// 从当前环境条目提取文本键作为编译期已知名;这只声明名称,不复制运行时值。
let names = vm
.dict_items(globals)
.unwrap()
.iter()
// 非文本键不能作为源码标识符,忽略它们而不向编译器提供伪名称。
.filter_map(|(name, _)| {
// 只把文本键转成声明名,其余键留在环境但不参与源码解析。
if let Value::Text(name) = name {
Some(name.clone())
} else {
None
}
})
.collect();
// 为临时源码添加测试来源身份,错误诊断据此显示文件名与位置。
let source = EndorReader::from_text("<classes>", source);
// 根据当前名字列表编译,前端或语义失败属于测试设置错误而非期望的 VM 错误。
let template = compile_source(&source, mode, &CompileOptions { globals: names }).unwrap();
// 显式加载模板取得当前 VM 的拥有代码根,禁止跨堆复用对象句柄。
let code = template.load(vm).unwrap();
// 先保存 Result,随后即使执行失败也必须释放当前代码根。
let result = vm.execute(code, globals);
// 临时代码的拥有根归还,成功结果若含对象仍有自己的根。
vm.release(code).unwrap();
result
}
#[test]
/// 由 Rust 测试框架调用:验证继承方法绑定真实子类实例、绑定参数数目扣除 self,单独回调根在清空环境后仍能动态派发。
fn source_inheritance_binding_and_dynamic_dispatch_keep_the_real_receiver_alive() {
// 隔离本场景堆、类型配置与执行状态,最后检查没有遗留对象。
let mut vm = Vm::new().unwrap();
// 测试持环境初始根,源码函数和类型声明可在内部引用此环境。
let globals = vm.new_dict().unwrap();
// 引导得到 Object/Type 拥有根,注册环境后分别归还测试根。
let roots = vm.bootstrap_types().unwrap();
// 把引导类型/宿主类型放入源码可访问的环境,再归还各自外部根。
for (name, object) in [("Object", roots.object), ("Type", roots.type_type)] {
vm.dict_set(globals, Value::Text(name.into()), Value::Object(object))
.unwrap();
vm.release(object).unwrap();
}
// Base.via 通过 self.read 动态派发;Child 覆盖 read,withEvent 用于检查绑定后的参数数量。
let source = r#"
class Base {
value: Number
function read(self: Base): Number { return self.value }
function via(self: Base): Number { return self.read() }
function withEvent(self: Base, event: Object): Object { return event }
}
class Child: Base {
label: String
function read(self: Child): Number { return 12 }
}
"#;
assert_eq!(
execute(&mut vm, globals, source, ParseMode::Module),
Value::Nil
);
// 读取临时 Child 实例的继承方法 via,返回绑定方法拥有根,方法必须保存实际接收者。
let callback = execute(
&mut vm,
globals,
"Child(9, \"ok\").via",
ParseMode::Expression,
);
// 回调必须是托管绑定方法,拥有根一直保留到环境清理与实际调用之后。
let Value::Object(callback) = callback else {
panic!("bound callback")
};
assert_eq!(
vm.object_store()
.positional_parameter_count(callback)
.unwrap(),
Some(0)
);
// 另取带 event 参数的绑定方法,检查 self 自动绑定后仍剩一个位置参数。
let with_event = execute(
&mut vm,
globals,
"Child(9, \"ok\").withEvent",
ParseMode::Expression,
);
// 将第二个回调结果解构为拥有句柄,稍后单独释放。
let Value::Object(with_event) = with_event else {
panic!("bound callback")
};
assert_eq!(
vm.object_store()
.positional_parameter_count(with_event)
.unwrap(),
Some(1)
);
// 参数计数检查结束,归还第二个绑定方法,避免它掩盖主回调的保活责任。
vm.release(with_event).unwrap();
// 先清环境的全局拥有边,打断函数/类型与环境之间可能的引用环。
vm.dict_clear(globals).unwrap();
// 归还环境外部根,剩余图是否存活只由其他拥有根决定。
vm.release(globals).unwrap();
// 收集已无外部根的类型/函数/环境环;仍由实例或回调拥有的图必须保留。
vm.collect_cycles();
// 绑定回调是接收者最后的外部可达入口;Base.via 内的 self.read 应派发到 Child.read。
// No global name or external instance root remains. The bound callback
// alone retains its receiver and can still dispatch to Child.read.
assert_eq!(
vm.call(callback, &[]).unwrap(),
Value::Number(12_i64.into())
);
// 完成清空环境后的动态派发验证,归还最后回调根。
vm.release(callback).unwrap();
// 收集已无外部根的类型/函数/环境环;仍由实例或回调拥有的图必须保留。
vm.collect_cycles();
assert_eq!(vm.object_count(), 0);
}
#[test]
/// 由 Rust 测试框架调用:验证重复字段及缺显式 self 在语义分析阶段被拒绝,不需创建 VM 或执行源码。
fn duplicate_members_and_missing_receivers_fail_before_execution() {
// 两个独立语义错误案例:同类重复字段,以及方法没有显式 self 参数。
for text in [
"class Bad { value: Number value: String }",
"class Bad { function read(): Number { return 1 } }",
] {
// 为临时源码添加测试来源身份,错误诊断据此显示文件名与位置。
let source = EndorReader::from_text("<invalid-class>", text);
assert!(
compile_source(
&source,
ParseMode::Module,
&CompileOptions {
globals: vec!["Object".into()]
}
)
.is_err()
);
}
}
#[test]
/// 由 Rust 测试框架调用:验证继承字段构造/赋值/删除检查、失败前后版本不变、动态名义注解重绑定与清理。
fn declared_field_checks_cover_construction_inheritance_writes_and_gc() {
// 隔离本场景堆、类型配置与执行状态,最后检查没有遗留对象。
let mut vm = Vm::new().unwrap();
// 开启源码注解及字段检查,下面的类型错误才会按约束拒绝。
vm.set_source_type_checks(true).unwrap();
// 测试持环境初始根,源码函数和类型声明可在内部引用此环境。
let globals = vm.new_dict().unwrap();
// 引导得到 Object/Type 拥有根,注册环境后分别归还测试根。
let roots = vm.bootstrap_types().unwrap();
vm.dict_set(
globals,
Value::Text("Object".into()),
Value::Object(roots.object),
)
.unwrap();
vm.release(roots.object).unwrap();
vm.release(roots.type_type).unwrap();
// 一次模块声明建立继承字段、名义对象字段及未知注解三种约束,真正的构造/写入在后面分别触发。
execute(
&mut vm,
globals,
r#"
class Base { count: Number }
class Child: Base { label: String }
class Holder { child: Base }
class Unknown { value: Missing }
"#,
ParseMode::Module,
);
// 每份交互源码只提交一条语句:先登记 Child 实例,再让 Holder 的字段持有同一实例。
// Interactive intentionally accepts one complete statement per source unit.
execute(
&mut vm,
globals,
r#"value = Child(9, "ok")"#,
ParseMode::Interactive,
);
execute(
&mut vm,
globals,
"holder = Holder(value)",
ParseMode::Interactive,
);
// 从 globals 借用已登记实例;测试不额外持该句柄的拥有根。
let Value::Object(value) = vm
.dict_get(globals, &Value::Text("value".into()))
.unwrap()
.unwrap()
else {
panic!("instance")
};
// 记录合法实例当前版本,后续失败的字段写入不得改变它。
let version = vm.object_store().change_version(value).unwrap();
// 覆盖继承字段、子字段、名义对象字段、未知注解及错误赋值,错误必须在更新前返回。
for source in [
r#"Child("bad", "ok")"#,
"Child(9, 2)",
"Holder(1)",
"Unknown(1)",
r#"value.count = "bad""#,
] {
// 错误应是类型不匹配或未知注解,不接受其他无关失败掩盖约束问题。
assert!(matches!(
try_execute(&mut vm, globals, source, ParseMode::Interactive),
Err(VmError::TypeMismatch { .. } | VmError::UnknownAnnotation(_))
));
}
assert_eq!(
vm.get_attr(value, "count").unwrap(),
Value::Number(9.into())
);
assert_eq!(vm.object_store().change_version(value).unwrap(), version);
assert!(vm.del_attr(value, "count").is_err());
vm.set_attr(value, "count", Value::Number(10.into()))
.unwrap();
// 用同名新类替换全局 Base,旧 value 的名义类型图仍保留最初的 Base 身份。
execute(&mut vm, globals, "class Base {}", ParseMode::Interactive);
// 其他名义类型名称在声明环境中动态解析;Base 重定义后 Holder 的字段约束应按新身份检查。
// Other nominal names resolve dynamically in the declaring environment.
assert!(matches!(
try_execute(&mut vm, globals, "Holder(value)", ParseMode::Expression),
Err(VmError::TypeMismatch { .. })
));
// 继承来的 count 保留其原声明字段元数据,环境同名类重定义不抹掉旧实例的数值约束。
// An inherited scalar field still belongs to its original declaration.
vm.set_attr(value, "count", Value::Number(11.into()))
.unwrap();
// 先清环境的全局拥有边,打断函数/类型与环境之间可能的引用环。
vm.dict_clear(globals).unwrap();
// 归还环境外部根,剩余图是否存活只由其他拥有根决定。
vm.release(globals).unwrap();
// 收集已无外部根的类型/函数/环境环;仍由实例或回调拥有的图必须保留。
vm.collect_cycles();
assert_eq!(vm.object_count(), 0);
}
#[test]
/// 由 Rust 测试框架调用:验证声明类自身的递归类型按原身份检查,即使全局同名类重定义;关闭检查允许临时写入 Nil。
fn checked_fields_keep_declaring_self_type_and_can_be_opted_out() {
// 隔离本场景堆、类型配置与执行状态,最后检查没有遗留对象。
let mut vm = Vm::new().unwrap();
// 开启源码注解及字段检查,下面的类型错误才会按约束拒绝。
vm.set_source_type_checks(true).unwrap();
// 测试持环境初始根,源码函数和类型声明可在内部引用此环境。
let globals = vm.new_dict().unwrap();
// 引导得到 Object/Type 拥有根,注册环境后分别归还测试根。
let roots = vm.bootstrap_types().unwrap();
vm.dict_set(
globals,
Value::Text("Object".into()),
Value::Object(roots.object),
)
.unwrap();
vm.release(roots.object).unwrap();
vm.release(roots.type_type).unwrap();
// Link.next 是递归且不可空的字段;Seed 声明展示继承,后续由可信宿主直接构造旧 Link 实例。
execute(
&mut vm,
globals,
r#"
class Link { next: Link }
class Seed: Link {}
"#,
ParseMode::Module,
);
// A recursive nonnullable field can be initialized by the trusted host in two steps.
// 从 globals 借用最初 Link 类型,后面同名重定义不应改变此类型的自引用身份。
let Value::Object(link) = vm
.dict_get(globals, &Value::Text("Link".into()))
.unwrap()
.unwrap()
else {
panic!("class")
};
// 宿主先分配空对象再附类型,用两步构造填入不可空的递归 next 字段。
let seed = vm.new_object().unwrap();
// 附上旧 Link 名义类型,此时先由可信宿主完成递归对象初始化。
vm.attach_type(seed, link).unwrap();
vm.set_attr(seed, "next", Value::Object(seed)).unwrap();
vm.dict_set(globals, Value::Text("old".into()), Value::Object(seed))
.unwrap();
// 新 Link 采用不同字段布局,只替换环境绑定;seed 及其 next 的原声明身份应不受影响。
execute(
&mut vm,
globals,
"class Link { value: Number }",
ParseMode::Interactive,
);
vm.set_attr(seed, "next", Value::Object(seed)).unwrap();
// 旧 Link 的自引用字段不可空,即使当前全局 Link 已换成另一个类。
assert!(matches!(
vm.set_attr(seed, "next", Value::Nil),
Err(VmError::TypeMismatch { .. })
));
// 显式关闭检查,允许下一步向不可空字段写 Nil;这不会改变声明本身。
vm.set_source_type_checks(false).unwrap();
vm.set_attr(seed, "next", Value::Nil).unwrap();
// 恢复检查配置后写回旧 Link 自身实例,验证声明身份和约束没有被关闭操作修改。
vm.set_source_type_checks(true).unwrap();
vm.set_attr(seed, "next", Value::Object(seed)).unwrap();
// 归还种子外部根;环境清理后,自引用环仍需循环收集器处理。
vm.release(seed).unwrap();
// 先清环境的全局拥有边,打断函数/类型与环境之间可能的引用环。
vm.dict_clear(globals).unwrap();
// 归还环境外部根,剩余图是否存活只由其他拥有根决定。
vm.release(globals).unwrap();
// 收集已无外部根的类型/函数/环境环;仍由实例或回调拥有的图必须保留。
vm.collect_cycles();
assert_eq!(vm.object_count(), 0);
}