docs(core): explain nominal type and callable arity regressions

This commit is contained in:
2026-11-15 18:01:25 +08:00
parent 96c689d64d
commit 4b93101e00
+106 -2
View File
@@ -1,14 +1,23 @@
//! 通过公开 Vm API 验证名义类型、显式能力和调用数量协议,由 Rust 测试框架逐场景调用。
//! 同名不是同身份;继承与实例关系分开;所有类型、方法、实例都由托管强边和外部根保活。
use endor_core::{TypeConstructor, Value, Vm, VmError};
#[test]
/// 区分子类型/实例关系、同名类型身份、不可变头部类型,以及实例独立保活定义图。
fn nominal_types_have_distinct_inheritance_and_instance_relations() {
// 各测试建立独立堆,不把其他场景的类型身份带入。
let mut vm = Vm::new().unwrap();
assert_eq!(vm.object_count(), 0);
// 显式自举 Object/Type 两个拥有根,新 VM 本身不预建语言类型。
let roots = vm.bootstrap_types().unwrap();
// 抽象 Block 作为继承基类,不代表其派生不能使用显式 Record 构造策略。
let block = vm
.new_type("Block", roots.object, TypeConstructor::Abstract)
.unwrap();
// Page 是 Block 子类型,同时自身是 Type 的实例。
let page = vm.new_type("Page", block, TypeConstructor::Record).unwrap();
// 同名 Page 再分配一次必须得到另一名义身份。
let other = vm.new_type("Page", block, TypeConstructor::Record).unwrap();
assert!(vm.is_subtype(page, block).unwrap());
assert!(!vm.is_subtype(block, page).unwrap());
@@ -17,6 +26,7 @@ fn nominal_types_have_distinct_inheritance_and_instance_relations() {
assert!(vm.is_instance(roots.object, roots.type_type).unwrap());
assert!(vm.is_instance(roots.type_type, roots.type_type).unwrap());
assert!(!vm.is_subtype(roots.object, roots.type_type).unwrap());
// call 构造的实例结果拥有一份根,后续清理必须显式 release。
let Value::Object(instance) = vm.call(page, &[]).unwrap() else {
panic!("instance required")
};
@@ -27,24 +37,32 @@ fn nominal_types_have_distinct_inheritance_and_instance_relations() {
assert!(vm.set_attr(instance, "type", Value::Object(other)).is_err());
assert!(vm.attach_type(instance, other).is_err());
assert!(vm.replace_payload(page, endor_core::Record).is_err());
// 归还每份测试拥有根,内部类型/基类/方法边不手动重复释放。
for object in [roots.object, roots.type_type, block, page, other] {
vm.release(object).unwrap();
}
// 清除无外部根的类型/元类型环;仍由实例可达的定义不得回收。
vm.collect_cycles();
// The instance keeps its definition, base and metatype reachable without
// any host-owned type registry. The unrelated same-name type was collected.
// 仅保留实例根仍可达定义、基类和元类型,无关的同名类型应被收集;没有堆外拥有注册表。
assert_eq!(vm.object_count(), 5);
assert!(vm.is_instance(instance, block).unwrap());
// 实例最后根归还后,其类型图不再因该实例保活。
vm.release(instance).unwrap();
// 清除无外部根的类型/元类型环;仍由实例可达的定义不得回收。
vm.collect_cycles();
assert_eq!(vm.object_count(), 0);
}
#[test]
/// 异堆类型附着失败不得改变头部;已有动态 type 字段也不得被托管类型附着覆盖。
fn foreign_type_edges_fail_without_changing_the_target() {
// 第一个 VM 拥有类型定义,不能把它附着到第二个 VM 的对象。
let mut first = Vm::new().unwrap();
// 第二个 VM 独立拥有对象和自己的类型自举图。
let mut second = Vm::new().unwrap();
// 显式自举 Object/Type 两个拥有根,新 VM 本身不预建语言类型。
let roots = first.bootstrap_types().unwrap();
// 待附着对象在第二堆,初始无名义类型且只有一根。
let object = second.new_object().unwrap();
assert_eq!(
second.attach_type(object, roots.object),
@@ -55,6 +73,7 @@ fn foreign_type_edges_fail_without_changing_the_target() {
second
.set_attr(object, "type", Value::Text("existing field".into()))
.unwrap();
// 同堆类型虽有效,仍应因对象已有动态 type 字段冲突而拒绝附着。
let local = second.bootstrap_types().unwrap();
assert!(second.attach_type(object, local.object).is_err());
assert_eq!(
@@ -64,9 +83,12 @@ fn foreign_type_edges_fail_without_changing_the_target() {
assert_eq!(second.type_of(object).unwrap(), None);
}
/// 能力测试辅助,生成指定显式参数数目的源码函数,含 self,总返回 Nil,不附完整类型签名。
fn contract_method(vm: &mut Vm, parameters: usize) -> endor_core::ObjectRef {
use endor_core::{CodeLayout, EndorCode, EndorCodeObject, Instruction, Opcode};
// 生成唯一参数名称,使 CodeLayout 参数前缀与局部布局一致。
let names: Vec<_> = (0..parameters).map(|index| format!("arg{index}")).collect();
// 测试用已验证指令,返回常量 Nil;数量由参数布局决定。
let code = EndorCode::from_instructions(
&[
Instruction::new(Opcode::LoadConst, 0),
@@ -80,27 +102,39 @@ fn contract_method(vm: &mut Vm, parameters: usize) -> endor_core::ObjectRef {
},
)
.unwrap();
// 分配代码载荷使常量引用(如有)成为托管边。
let code = vm.allocate(EndorCodeObject::new(code)).unwrap();
// 函数全局字典用于注解解析和环境持有,函数内部边不依赖测试根。
let globals = vm.new_dict().unwrap();
// 函数构造校验布局并持有代码/全局/默认值引用,返回一份根。
let function = vm
.new_function("contract method", code, globals, vec![], vec![])
.unwrap();
// 辅助函数归还临时代码根,函数内部边仍保留代码。
vm.release(code).unwrap();
// 辅助函数归还临时全局根,函数环境边继续保活。
vm.release(globals).unwrap();
function
}
#[test]
/// 显式能力可继承且按身份区分,错误方法/派生声明不能破坏已有契约,能力边维持定义存活。
fn explicit_traits_inherit_validate_overrides_and_own_definition_edges() {
use endor_core::MethodRequirement;
// 各测试建立独立堆,不把其他场景的类型身份带入。
let mut vm = Vm::new().unwrap();
// 显式自举 Object/Type 两个拥有根,新 VM 本身不预建语言类型。
let roots = vm.bootstrap_types().unwrap();
// 可构造基类作为能力继承与方法更新测试起点。
let base = vm
.new_type("Base", roots.object, TypeConstructor::Record)
.unwrap();
// 含 self 和一个普通参数,满足 render 的最小能力数量要求。
let valid = contract_method(&mut vm, 2);
// 仅 self 一个参数,无法接受 render 所需普通参数。
let invalid = contract_method(&mut vm, 1);
vm.define_method(base, "render", valid).unwrap();
// 最小能力声明只要求方法及显式参数数,不推断结构等价。
let capability = vm
.new_trait(
"Drawable",
@@ -110,10 +144,12 @@ fn explicit_traits_inherit_validate_overrides_and_own_definition_edges() {
}],
)
.unwrap();
// 同名能力仍为不同身份,声明一个不会自动支持另一个。
let same_name = vm.new_trait("Drawable", vec![]).unwrap();
assert!(!vm.implements_trait(base, capability).unwrap());
vm.implement_trait(base, capability).unwrap();
assert!(!vm.implements_trait(base, same_name).unwrap());
// 已有派生类用于核对继承能力/覆盖方法,方法来源由真实继承查找决定。
let child = vm
.new_class("Child", base, vec![], std::collections::BTreeMap::new())
.unwrap();
@@ -130,12 +166,15 @@ fn explicit_traits_inherit_validate_overrides_and_own_definition_edges() {
vm.new_class("Bad", base, vec![], [("render".into(), invalid)].into())
.is_err()
);
// call 构造的实例结果拥有一份根,后续清理必须显式 release。
let Value::Object(instance) = vm.call(child, &[]).unwrap() else {
panic!("instance required");
};
// 归还测试根后类型声明的能力强边仍应保活,supports 不能依赖堆外注册表。
vm.release(capability).unwrap();
assert!(vm.supports(instance, capability).unwrap());
assert!(vm.replace_payload(capability, endor_core::Record).is_err());
// 归还每份测试拥有根,内部类型/基类/方法边不手动重复释放。
for object in [
roots.object,
roots.type_type,
@@ -148,24 +187,33 @@ fn explicit_traits_inherit_validate_overrides_and_own_definition_edges() {
] {
vm.release(object).unwrap();
}
// 清除无外部根的类型/元类型环;仍由实例可达的定义不得回收。
vm.collect_cycles();
assert_eq!(vm.object_count(), 0);
}
#[test]
/// 后注册能力必须核对已有后代,父方法更新也不能破坏后代自己声明的能力。
fn late_trait_registration_and_ancestor_updates_validate_existing_descendants() {
use endor_core::MethodRequirement;
// 各测试建立独立堆,不把其他场景的类型身份带入。
let mut vm = Vm::new().unwrap();
// 显式自举 Object/Type 两个拥有根,新 VM 本身不预建语言类型。
let roots = vm.bootstrap_types().unwrap();
// 可构造基类作为能力继承与方法更新测试起点。
let base = vm
.new_type("Base", roots.object, TypeConstructor::Record)
.unwrap();
// 满足 self 加一个参数的源码函数,用于恢复合法方法。
let good = contract_method(&mut vm, 2);
// 少一个参数的源码函数,不可用于已声明能力的方法。
let bad = contract_method(&mut vm, 1);
vm.define_method(base, "render", good).unwrap();
// 已有派生类用于核对继承能力/覆盖方法,方法来源由真实继承查找决定。
let child = vm
.new_class("Child", base, vec![], [("render".into(), bad)].into())
.unwrap();
// 最小能力声明只要求方法及显式参数数,不推断结构等价。
let capability = vm
.new_trait(
"Drawable",
@@ -178,6 +226,7 @@ fn late_trait_registration_and_ancestor_updates_validate_existing_descendants()
assert!(vm.implement_trait(base, capability).is_err());
assert!(!vm.implements_trait(base, capability).unwrap());
vm.define_method(child, "render", good).unwrap();
// 不覆盖父方法的后代自行声明能力,父方法替换也必须顾及它。
let inheriting = vm
.new_class(
"Inheriting",
@@ -192,6 +241,7 @@ fn late_trait_registration_and_ancestor_updates_validate_existing_descendants()
vm.object_store().lookup_method(base, "render").unwrap(),
Some(good)
);
// 第三堆查询已有类型/能力应报 ForeignHandle,不是简单 false。
let foreign = Vm::new().unwrap();
assert_eq!(
foreign.implements_trait(base, capability),
@@ -200,21 +250,28 @@ fn late_trait_registration_and_ancestor_updates_validate_existing_descendants()
}
#[test]
/// Function 配置覆盖源码/原生/绑定方法,但普通可调用类不属于函数;配置不能切换。
fn configured_function_identity_covers_source_native_and_bound_methods() {
use endor_core::{NativeFunction, SourceSignature};
// 各测试建立独立堆,不把其他场景的类型身份带入。
let mut vm = Vm::new().unwrap();
// 显式自举 Object/Type 两个拥有根,新 VM 本身不预建语言类型。
let roots = vm.bootstrap_types().unwrap();
// 显式注册函数名义类型,构造为 Abstract 避免把普通记录伪造成函数载荷。
let function_type = vm
.new_type("Function", roots.object, TypeConstructor::Abstract)
.unwrap();
vm.set_function_type(function_type).unwrap();
// 源码函数应由分配入口自动附着已配置 Function。
let source = contract_method(&mut vm, 1);
// 原生载荷也按 is_function 分类附着类型,数量协议仍独立。
let native = vm
.allocate(NativeFunction::new("native", |_| Ok(Value::Nil)))
.unwrap();
assert!(vm.is_instance(source, function_type).unwrap());
assert!(vm.is_instance(native, function_type).unwrap());
vm.attach_type(source, function_type).unwrap();
// 普通类可调用但不是 Function;类方法表持有函数边。
let class = vm
.new_class(
"Data",
@@ -223,15 +280,18 @@ fn configured_function_identity_covers_source_native_and_bound_methods() {
[("read".into(), source)].into(),
)
.unwrap();
// call 构造的实例结果拥有一份根,后续清理必须显式 release。
let Value::Object(instance) = vm.call(class, &[]).unwrap() else {
panic!("instance required");
};
// 绑定结果持有函数与 receiver,且本身应自动取得 Function 类型。
let Value::Object(bound) = vm.bind_attr(instance, "read").unwrap() else {
panic!("method required");
};
assert!(vm.is_instance(bound, function_type).unwrap());
assert!(!vm.is_instance(class, function_type).unwrap());
assert!(vm.set_function_type(class).is_err());
// 零参数代码却附一项参数注解,源码签名构造阶段即应拒绝。
let invalid = endor_core::EndorCode::from_instructions(
&[
endor_core::Instruction::new(endor_core::Opcode::LoadConst, 0),
@@ -247,6 +307,7 @@ fn configured_function_identity_covers_source_native_and_bound_methods() {
method_owner: None,
});
assert!(invalid.is_err());
// 归还每份测试拥有根,内部类型/基类/方法边不手动重复释放。
for object in [
source,
native,
@@ -259,27 +320,34 @@ fn configured_function_identity_covers_source_native_and_bound_methods() {
] {
vm.release(object).unwrap();
}
// 清除无外部根的类型/元类型环;仍由实例可达的定义不得回收。
vm.collect_cycles();
assert_eq!(vm.object_count(), 0);
}
#[test]
/// 源码返回类型不符时检查结果和已解析类型根的计数恢复,无隐藏执行根泄漏。
fn failed_nominal_return_releases_value_and_captured_type_roots() {
use endor_core::{
CodeLayout, EndorCode, EndorCodeObject, Instruction, Opcode, SourceSignature,
};
// 各测试建立独立堆,不把其他场景的类型身份带入。
let mut vm = Vm::new().unwrap();
vm.set_source_type_checks(true).unwrap();
// 显式自举 Object/Type 两个拥有根,新 VM 本身不预建语言类型。
let roots = vm.bootstrap_types().unwrap();
// 预期返回类型通过全局字典解析,执行时另保留返回定义根。
let expected = vm
.new_type("Expected", roots.object, TypeConstructor::Record)
.unwrap();
let other = vm
.new_type("Other", roots.object, TypeConstructor::Record)
.unwrap();
// 错误返回对象先有测试根,代码常量表还会独立持有它。
let Value::Object(value) = vm.call(other, &[]).unwrap() else {
panic!("instance required");
};
// 函数全局字典用于注解解析和环境持有,函数内部边不依赖测试根。
let globals = vm.new_dict().unwrap();
vm.dict_set(
globals,
@@ -287,6 +355,7 @@ fn failed_nominal_return_releases_value_and_captured_type_roots() {
Value::Object(expected),
)
.unwrap();
// 测试用已验证指令,返回常量 Nil;数量由参数布局决定。
let code = EndorCode::from_instructions(
&[
Instruction::new(Opcode::LoadConst, 0),
@@ -302,11 +371,15 @@ fn failed_nominal_return_releases_value_and_captured_type_roots() {
method_owner: None,
})
.unwrap();
// 分配代码载荷使常量引用(如有)成为托管边。
let code = vm.allocate(EndorCodeObject::new(code)).unwrap();
// 函数构造校验布局并持有代码/全局/默认值引用,返回一份根。
let function = vm
.new_function("bad return", code, globals, vec![], vec![])
.unwrap();
// 记录调用前定义计数,返回失败后帧持有的类型根应全部归还。
let before_type = vm.ref_count(expected).unwrap();
// 记录错误结果计数,失败后不应遗留栈根或交付根。
let before_value = vm.ref_count(value).unwrap();
assert!(matches!(
vm.call(function, &[]),
@@ -314,6 +387,7 @@ fn failed_nominal_return_releases_value_and_captured_type_roots() {
));
assert_eq!(vm.ref_count(expected).unwrap(), before_type);
assert_eq!(vm.ref_count(value).unwrap(), before_value);
// 归还每份测试拥有根,内部类型/基类/方法边不手动重复释放。
for object in [
function,
code,
@@ -326,33 +400,43 @@ fn failed_nominal_return_releases_value_and_captured_type_roots() {
] {
vm.release(object).unwrap();
}
// 清除无外部根的类型/元类型环;仍由实例可达的定义不得回收。
vm.collect_cycles();
assert_eq!(vm.object_count(), 0);
}
#[derive(Debug)]
/// 借用状态身份的测试分派器,包装载荷通过 captures 显式 trace 保活它。
struct CountedNative(endor_core::ObjectRef);
impl endor_core::NativeCallable for CountedNative {
/// CountedNative 发布含 self 的原生位置参数区间,供 VM 在副作用前检查。
fn arity(&self) -> Option<endor_core::CallArity> {
Some(endor_core::CallArity::range(1, 2))
}
/// 数量检查通过后才设置 called 标志,返回直接 Nil,用于观测是否进入回调。
fn invoke(
&self,
cx: &mut endor_core::NativeContext<'_>,
_: &[Value],
) -> Result<endor_core::NativeResult, VmError> {
// 回调唯一副作用,错误数量调用后的 called=false 可证明回调未进入。
cx.set_attr(self.0, "called", Value::Bool(true))?;
Ok(endor_core::NativeResult::Return(Value::Nil))
}
}
#[test]
/// 验证数量错误先于副作用、绑定参数扣减、能力更新拒绝未知/错误数量及畸形元数据禁止执行。
fn native_arity_checks_precede_effects_and_traits_require_published_signatures() {
use endor_core::{CallArity, ContextNativeFunction, MethodRequirement, NativeFunction};
// 各测试建立独立堆,不把其他场景的类型身份带入。
let mut vm = Vm::new().unwrap();
// 显式自举 Object/Type 两个拥有根,新 VM 本身不预建语言类型。
let roots = vm.bootstrap_types().unwrap();
// 副作用标志载体被 CountedNative 的显式 captures 边保活。
let state = vm.new_object().unwrap();
vm.set_attr(state, "called", Value::Bool(false)).unwrap();
// 原生载荷也按 is_function 分类附着类型,数量协议仍独立。
let native = vm
.allocate(ContextNativeFunction::new(
"counted",
@@ -366,6 +450,7 @@ fn native_arity_checks_precede_effects_and_traits_require_published_signatures()
Err(VmError::ArgumentCount { .. })
));
assert_eq!(vm.get_attr(state, "called").unwrap(), Value::Bool(false));
// 普通类可调用但不是 Function;类方法表持有函数边。
let class = vm
.new_class(
"Data",
@@ -374,6 +459,7 @@ fn native_arity_checks_precede_effects_and_traits_require_published_signatures()
[("run".into(), native)].into(),
)
.unwrap();
// 最小能力声明只要求方法及显式参数数,不推断结构等价。
let capability = vm
.new_trait(
"Runnable",
@@ -384,20 +470,24 @@ fn native_arity_checks_precede_effects_and_traits_require_published_signatures()
)
.unwrap();
vm.implement_trait(class, capability).unwrap();
// call 构造的实例结果拥有一份根,后续清理必须显式 release。
let Value::Object(instance) = vm.call(class, &[]).unwrap() else {
panic!("instance")
};
// 绑定结果持有函数与 receiver,且本身应自动取得 Function 类型。
let Value::Object(bound) = vm.bind_attr(instance, "run").unwrap() else {
panic!("bound")
};
assert_eq!(vm.call_arity(bound).unwrap(), Some(CallArity::range(0, 1)));
assert_eq!(vm.call(bound, &[]).unwrap(), Value::Nil);
assert_eq!(vm.get_attr(state, "called").unwrap(), Value::Bool(true));
// 未发布 arity 的旧原生函数可独立调用,但不能证明满足已声明最小能力。
let unknown = vm
.allocate(NativeFunction::new("unknown", |_| Ok(Value::Nil)))
.unwrap();
assert_eq!(vm.call_arity(unknown).unwrap(), None);
assert!(vm.define_method(class, "run", unknown).is_err());
// 固定三参数函数与要求 self-only 的 run 不兼容,替换不能提交。
let wrong = vm
.allocate(NativeFunction::new("wrong", |_| Ok(Value::Nil)).with_arity(CallArity::exact(3)))
.unwrap();
@@ -406,6 +496,7 @@ fn native_arity_checks_precede_effects_and_traits_require_published_signatures()
vm.object_store().lookup_method(class, "run").unwrap(),
Some(native)
);
// 开放上界的原生区间,绑定后只扣下界 self,最大值仍须保持 usize::MAX。
let variadic = vm
.allocate(
NativeFunction::new("variadic", |_| Ok(Value::Nil))
@@ -413,6 +504,7 @@ fn native_arity_checks_precede_effects_and_traits_require_published_signatures()
)
.unwrap();
vm.define_method(class, "many", variadic).unwrap();
// 绑定开放区间函数,测试可见数量而不实际执行任意多参数。
let Value::Object(variadic_bound) = vm.bind_attr(instance, "many").unwrap() else {
panic!("bound")
};
@@ -422,6 +514,7 @@ fn native_arity_checks_precede_effects_and_traits_require_published_signatures()
);
vm.release(variadic_bound).unwrap();
vm.release(variadic).unwrap();
// 颠倒区间并放置 panic 回调,数量元数据错误必须在调用回调前返回。
let malformed = vm
.allocate(
NativeFunction::new("malformed", |_| panic!("invalid metadata must not execute"))
@@ -430,6 +523,7 @@ fn native_arity_checks_precede_effects_and_traits_require_published_signatures()
.unwrap();
assert!(vm.call_arity(malformed).is_err());
assert!(vm.call(malformed, &[]).is_err());
// 归还每份测试拥有根,内部类型/基类/方法边不手动重复释放。
for object in [
roots.object,
roots.type_type,
@@ -445,15 +539,20 @@ fn native_arity_checks_precede_effects_and_traits_require_published_signatures()
] {
vm.release(object).unwrap();
}
// 清除无外部根的类型/元类型环;仍由实例可达的定义不得回收。
vm.collect_cycles();
assert_eq!(vm.object_count(), 0);
}
#[test]
/// 验证源码默认值缩小必填区间,同时没有数量元数据的旧原生调用仍保持可调用。
fn unified_arity_preserves_source_defaults_and_legacy_native_calls() {
use endor_core::{CallArity, CodeLayout, EndorCode, Instruction, NativeFunction, Opcode};
// 各测试建立独立堆,不把其他场景的类型身份带入。
let mut vm = Vm::new().unwrap();
// 函数全局字典用于注解解析和环境持有,函数内部边不依赖测试根。
let globals = vm.new_dict().unwrap();
// 测试用已验证指令,返回常量 Nil;数量由参数布局决定。
let code = EndorCode::from_instructions(
&[
Instruction::new(Opcode::LoadConst, 0),
@@ -467,7 +566,9 @@ fn unified_arity_preserves_source_defaults_and_legacy_native_calls() {
},
)
.unwrap();
// 统一分配代码拥有根,后续函数也持代码内部边。
let code = vm.new_code(code).unwrap();
// 函数构造校验布局并持有代码/全局/默认值引用,返回一份根。
let function = vm
.new_function("optional", code, globals, vec![Value::Nil], vec![])
.unwrap();
@@ -476,14 +577,17 @@ fn unified_arity_preserves_source_defaults_and_legacy_native_calls() {
Some(CallArity::range(0, 1))
);
assert_eq!(vm.call(function, &[]).unwrap(), Value::Nil);
// 无数量元数据仍是兼容原生函数,None 不应被误解为不支持调用。
let legacy = vm
.allocate(NativeFunction::new("legacy", |_| Ok(Value::Nil)))
.unwrap();
assert_eq!(vm.call_arity(legacy).unwrap(), None);
assert_eq!(vm.call(legacy, &[]).unwrap(), Value::Nil);
// 归还每份测试拥有根,内部类型/基类/方法边不手动重复释放。
for object in [globals, code, function, legacy] {
vm.release(object).unwrap();
}
// 清除无外部根的类型/元类型环;仍由实例可达的定义不得回收。
vm.collect_cycles();
assert_eq!(vm.object_count(), 0);
}