docs: explain match assertions and annotation type distinctions

This commit is contained in:
2026-11-15 19:14:55 +08:00
parent 903b3bbf9d
commit 57cd32eb5c
16 changed files with 58 additions and 6 deletions
+3
View File
@@ -148,8 +148,10 @@ fn dictionaries_keep_order_and_first_key_and_own_keys_and_values() {
);
assert_eq!(vm.dict_items(dict).unwrap()[0].0, Value::Object(key));
assert_eq!(vm.dict_items(dict).unwrap().len(), 2);
// 字典覆盖旧值后,old 已无任何拥有根,访问必须报告失效句柄。
assert!(matches!(vm.object(old), Err(VmError::InvalidHandle)));
assert!(vm.dict_delete(dict, &text("answer")).unwrap());
// 删除 answer 同时释放保留的首键对象,等价 Text 键不会替它延长寿命。
assert!(matches!(vm.object(key), Err(VmError::InvalidHandle)));
assert!(!vm.dict_delete(dict, &text("missing")).unwrap());
vm.dict_clear(dict).unwrap();
@@ -227,6 +229,7 @@ fn cells_share_rebinding_distinguish_empty_and_none_and_keep_stable_objects() {
vm.cell_set(cell, Value::Object(child)).unwrap();
assert_eq!(vm.ref_count(child).unwrap(), 1);
vm.cell_clear(cell).unwrap();
// 清空 Cell 归还子对象最后的内部边,child 不能仍然可访问。
assert!(matches!(vm.object(child), Err(VmError::InvalidHandle)));
assert_eq!(vm.cell_get(cell), Err(VmError::EmptyCell));
}
+9 -4
View File
@@ -237,6 +237,7 @@ fn malformed_wordcode_and_unaligned_jumps_are_rejected_at_construction() {
vec![255, 1, 255, 1, 255, 1, 255, 1, 1, 0],
vec![13, 1],
] {
// 每份非法物理编码都必须在代码构造阶段拒绝,不能等到 VM 取指才失败。
assert!(matches!(
EndorCode::new(bytes, vec![Value::Nil], vec![], vec![]),
Err(VmError::InvalidBytecode { .. })
@@ -482,6 +483,7 @@ fn argument_count_and_foreign_argument_failures_do_not_retain_inputs() {
let function = vm
.new_function("identity", code, globals, vec![], vec![])
.unwrap();
// 无实参调用必须准确报告缺少唯一必需参数,不把缺参隐式当作 Nil。
assert!(matches!(
vm.call(function, &[]),
Err(VmError::ArgumentCount {
@@ -490,6 +492,7 @@ fn argument_count_and_foreign_argument_failures_do_not_retain_inputs() {
..
})
));
// 两个实参超过该函数的最大数量一,预检应在进入函数体前拒绝。
assert!(matches!(
vm.call(function, &[Value::Nil, Value::Nil]),
Err(VmError::ArgumentCount {
@@ -1011,6 +1014,7 @@ fn unbound_names_deleted_locals_and_invalid_null_values_fail_cleanly() {
vec![],
CodeLayout::default(),
);
// Null 是 VM 协议哨兵而非语言值,源码执行把它用于非法值路径时必须拒绝。
assert!(matches!(
vm.execute(null, globals),
Err(VmError::InvalidBytecode { .. })
@@ -1699,6 +1703,7 @@ fn suspended_read_only_call_keeps_roots_and_releases_them_on_completion() {
1,
)
.unwrap();
// 首量子只消耗一条 Nop,应保存暂停帧并报告累计一条,不能提前完成。
assert!(matches!(
progress,
ExecutionProgress::Pending { instructions: 1 }
@@ -1852,13 +1857,13 @@ fn managed_coroutines_interleave_without_replaying_writes() {
let second = vm
.new_coroutine(function, &[], ExecutionLimits::default())
.unwrap();
// 推进第一份到首次 StoreGlobal 之后,累计两条指令。
assert!(matches!(
// 推进第一份到首次 StoreGlobal 之后,累计两条指令。
vm.resume_coroutine(first, 2).unwrap(),
CoroutineProgress::Pending { instructions: 2 }
));
// 第二份也停在首次写入之后,后续都不应重放这个前缀。
assert!(matches!(
// 第二份也停在首次写入之后,后续都不应重放这个前缀。
vm.resume_coroutine(second, 2).unwrap(),
CoroutineProgress::Pending { instructions: 2 }
));
@@ -1965,13 +1970,13 @@ fn managed_coroutine_hard_budget_accumulates_and_error_releases_frames() {
},
)
.unwrap();
// 首量子只执行 Nop 并停放,累计已用一条。
assert!(matches!(
// 首量子只执行 Nop 并停放,累计已用一条。
vm.resume_coroutine(coroutine, 1).unwrap(),
CoroutineProgress::Pending { instructions: 1 }
));
// 第二次执行 LoadConst 后预算耗尽,尚未 Return,因此应终止而非再次 Pending。
assert!(matches!(
// 第二次执行 LoadConst 后预算耗尽,尚未 Return,因此应终止而非再次 Pending。
vm.resume_coroutine(coroutine, 1),
Err(VmError::ExecutionLimit("instructions"))
));
+6
View File
@@ -106,6 +106,7 @@ fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation()
"Number",
Value::Nil,
);
// 子方法缩窄参数可接受范围会破坏替换关系,必须在建类时报告覆盖违规。
assert!(matches!(
class(&mut vm, "Narrow", base, narrow),
Err(VmError::OverrideViolation {
@@ -133,6 +134,7 @@ fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation()
"Object",
Value::Nil,
);
// 子方法放宽返回类型会破坏父方法承诺,必须在建类时拒绝。
assert!(matches!(
class(&mut vm, "Broad", numeric_type, broad),
Err(VmError::OverrideViolation {
@@ -305,6 +307,7 @@ fn global_rebinding_is_rechecked_before_the_source_body_and_recovers() {
vm.attach_type(receiver, child).unwrap();
vm.dict_set(globals, Value::Text("Actual".into()), Value::Object(other))
.unwrap();
// Actual 的全局类型绑定变化后,调用前重新检查覆盖关系,失败时不能运行方法体。
assert!(matches!(
vm.call(child_fn, &[Value::Object(receiver)]),
Err(VmError::OverrideViolation { .. })
@@ -404,6 +407,7 @@ fn native_overrides_preserve_union_inputs_and_do_not_expand_effects() {
vec![],
))
.unwrap();
// 只读父方法不能被已知有写效果的子方法替换,更新必须在提交前失败。
assert!(matches!(
vm.define_method(child, "check", effect_fn),
Err(VmError::OverrideViolation {
@@ -423,6 +427,7 @@ fn native_overrides_preserve_union_inputs_and_do_not_expand_effects() {
vec![],
))
.unwrap();
// 子方法仅接受 Number 会丢失父联合参数的 String 候选,不能缩窄父方法可接受的输入。
assert!(matches!(
vm.define_method(child, "check", narrow_fn),
Err(VmError::OverrideViolation {
@@ -498,6 +503,7 @@ fn unknown_forward_names_defer_until_call_without_entering_the_body() {
.unwrap();
vm.dict_set(globals, Value::Text("Later".into()), Value::Object(later))
.unwrap();
// Later 从未知名解析成不兼容名义类型后,错误应转为覆盖违规且仍在方法体前发生。
assert!(matches!(
vm.call(
child_fn,
+7 -1
View File
@@ -442,10 +442,12 @@ fn native_types_preflight_arguments_and_clean_wrong_result_roots() {
))
.unwrap();
assert!(
// 错误实参在回调前由 parameter 1 约束拒绝,后面的计数零验证未进入宿主代码。
matches!(vm.call(function, &[Value::Text("bad".into())]), Err(VmError::TypeMismatch { position, .. }) if position == "parameter 1")
);
assert_eq!(calls.load(Ordering::SeqCst), 0);
assert!(
// 合法数值允许回调执行一次,但宿主返回值违约应明确定位 return。
matches!(vm.call(function, &[Value::Number(7.into())]), Err(VmError::TypeMismatch { position, .. }) if position == "return")
);
assert_eq!(calls.load(Ordering::SeqCst), 1);
@@ -519,8 +521,10 @@ fn delegated_source_results_obey_every_native_contract_without_extra_frames() {
Value::Text("source result".into())
);
assert!(
// 多层原生委托结果返回外层时,仍需兑现 outer 自己的返回约束并标明其名称。
matches!(vm.call_with_limits(outer, &[], limits), Err(VmError::TypeMismatch { function, position, .. }) if function == "outer" && position == "return")
);
// 零源码帧预算必须由受委托源码入口拒绝,不能因原生委托不占帧而绕过限制。
assert!(matches!(
vm.call_with_limits(
outer,
@@ -656,7 +660,7 @@ fn legacy_native_signatures_check_optional_variadic_types_and_metadata() {
}
// 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。
let mut vm = Vm::new().unwrap();
// 分配上下文载荷,签名类型与显式 captures 各自通过 trace 保活。
// 分配旧式 NativeFunction 载荷,签名引用的类型定义仍通过 trace 保活。
let function = vm
.allocate(
NativeFunction::new("legacy", echo).with_signature(CallSignature {
@@ -678,6 +682,7 @@ fn legacy_native_signatures_check_optional_variadic_types_and_metadata() {
);
assert!(vm.call(function, &[Value::Bool(true)]).is_err());
assert!(
// 可变参数允许空列表,但旧式 echo 此时返回 Nil,不符合声明的 Number 返回约束。
matches!(vm.call(function, &[]), Err(VmError::TypeMismatch { position, .. }) if position == "return")
);
// 故意同时配置互相矛盾的签名和独立 arity,查询/调用都应报元数据错误。
@@ -835,6 +840,7 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met
);
}
assert!(
// 联合都不匹配时诊断需完整保留 String | Bool 候选,不只显示最后尝试的一项。
matches!(vm.call(function, &[Value::Number(3.into())]), Err(VmError::TypeMismatch { expected, .. }) if expected == "String | Bool")
);
assert_eq!(calls.load(Ordering::SeqCst), 3);
+4
View File
@@ -164,6 +164,7 @@ fn explicit_references_control_lifetime_and_copies_are_borrowed() {
vm.release(owned).unwrap();
assert_eq!(drops.load(Ordering::SeqCst), 1);
assert_eq!(vm.object_count(), 0);
// 最后拥有根释放后,复制来的借用句柄必须失效,不能当作额外拥有根。
assert!(matches!(vm.object(borrowed), Err(VmError::InvalidHandle)));
assert_eq!(vm.release(borrowed), Err(VmError::InvalidHandle));
}
@@ -210,6 +211,7 @@ fn replacing_the_same_edge_does_not_destroy_the_target() {
assert_eq!(vm.ref_count(target).unwrap(), 1);
vm.set_attr(owner, "child", Value::Number(7_i64.into()))
.unwrap();
// 属性从对象改为数值会归还原目标最后的边,因此旧 target 句柄应失效。
assert!(matches!(vm.object(target), Err(VmError::InvalidHandle)));
assert_eq!(
vm.get_attr(owner, "child").unwrap(),
@@ -350,7 +352,9 @@ fn cycles_remain_while_rooted_and_are_collected_when_unreachable() {
assert_eq!(vm.collect_cycles(), 2);
assert_eq!(vm.collect_cycles(), 0);
assert_eq!(drops.load(Ordering::SeqCst), 2);
// 无根环回收后第一个旧句柄失效,不能只依赖析构计数判断槽是否仍可访问。
assert!(matches!(vm.object(first), Err(VmError::InvalidHandle)));
// 同一垃圾环中的第二个对象也必须失效,避免只回收部分环。
assert!(matches!(vm.object(second), Err(VmError::InvalidHandle)));
}
+1
View File
@@ -224,6 +224,7 @@ fn typed_source_methods_enforce_their_annotations_even_when_global_checks_are_of
Some(Value::Number(0.into()))
);
assert!(
// 参数合法时方法体可运行,但违背能力方法的返回约束应在出口定位 return。
matches!(vm.call(function, &[Value::Object(receiver), Value::Number(1.into())]), Err(VmError::TypeMismatch { position, .. }) if position=="return")
);
assert_eq!(
+2
View File
@@ -154,6 +154,7 @@ fn explicit_traits_inherit_validate_overrides_and_own_definition_edges() {
.new_class("Child", base, vec![], std::collections::BTreeMap::new())
.unwrap();
assert!(vm.implements_trait(child, capability).unwrap());
// 祖先方法更新不得破坏已声明后代能力,失败后原方法仍应保持不变。
assert!(matches!(
vm.define_method(base, "render", invalid),
Err(VmError::TraitViolation { .. })
@@ -446,6 +447,7 @@ fn native_arity_checks_precede_effects_and_traits_require_published_signatures()
))
.unwrap();
assert_eq!(vm.call_arity(native).unwrap(), Some(CallArity::range(1, 2)));
// 零实参违反原生函数声明的 arity,必须在回调修改 called 状态之前拒绝。
assert!(matches!(
vm.call(native, &[]),
Err(VmError::ArgumentCount { .. })