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 { .. })
+2
View File
@@ -26,6 +26,7 @@ fn function_bodies_accept_method_statements_and_optional_trailing_return() {
.body
.statements
.iter()
// 两个调用都应是普通 Expression 语句,不能被误识别为返回或绑定。
.all(|s| matches!(s.kind, StmtKind::Expression(_)))
);
assert!(parse_ast("function empty(): Unit {}", ParseMode::Module).is_ok());
@@ -258,6 +259,7 @@ fn json_interfaces_reject_unknown_fields_schema_changes_and_invalid_ranges() {
kind: ExprKind::Name("値".into()),
span: Span::new(1, 3),
});
// 结构范围虽非逆序,起点切断三字节日文字符,仍须在提供原文的校验阶段失败。
assert!(matches!(
AstDocument::new(root).unwrap().validate_source("値"),
Err(AstError::InvalidSpan(_))
+2
View File
@@ -291,6 +291,7 @@ fn uri_snapshots_require_a_revision_and_preserve_it_in_source_diagnostics() {
revision: " \t",
bytes: b"1",
};
// 字节有效不能补救缺版本身份,空白版本标签应单独报告 MissingRevision。
assert!(matches!(
EndorReader::from_endor(uri, &missing).unwrap_err().kind,
SourceReadErrorKind::MissingRevision
@@ -336,6 +337,7 @@ fn invalid_uris_do_not_call_the_provider_and_denials_do_not_fall_back_to_files()
"endor:space here",
"endor:bad\nname",
] {
// 每个非法定位符都必须先被基础 URI 校验拒绝,后面的计数零验证未访问宿主。
assert!(matches!(
EndorReader::from_endor(uri, &provider).unwrap_err().kind,
SourceReadErrorKind::InvalidEndorUri
+1
View File
@@ -190,6 +190,7 @@ fn arithmetic_precedence_and_left_associativity_form_the_expected_tree() {
let ExprKind::Unary { operand, .. } = nested.kind else {
panic!("expected outer negate");
};
// 外层负号的操作数仍是 Negate,证明解析器保留两个前缀节点而未提前计算数值。
assert!(matches!(
operand.kind,
ExprKind::Unary {
+3
View File
@@ -926,7 +926,9 @@ mod tests {
generation: 1,
..reference
};
// 槽索引越界时,即使堆身份和代际来自有效对象,也必须拒绝只读访问。
assert!(matches!(heap.get(invalid), Err(VmError::InvalidHandle)));
// 可变访问同样检查代际,伪造旧代际不能借出当前对象。
assert!(matches!(heap.get_mut(stale), Err(VmError::InvalidHandle)));
}
@@ -942,6 +944,7 @@ mod tests {
let second = heap.allocate(Box::new(Record)).unwrap();
assert_eq!(first.index, second.index);
assert_ne!(first.generation, second.generation);
// 复用同一槽但代际变化后,旧句柄必须失效,不能别名到新对象。
assert!(matches!(heap.get(first), Err(VmError::InvalidHandle)));
// 内部故障注入:把槽及对象身份同步改成最高代际,再验证释放后的永久退休。
heap.slots[second.index].generation = u64::MAX;
+1
View File
@@ -119,6 +119,7 @@ impl CallType {
pub fn is_any(&self) -> bool {
self.alternatives()
.iter()
// 只检测是否存在 Any 候选,不据此省略联合中其他定义引用的验证或保活。
.any(|value| matches!(value, crate::AnnotationType::Any))
}
/// 调用/签名检查确认联合非空,且每个托管类型/能力引用有效并有正确载荷。
+2
View File
@@ -608,8 +608,10 @@ impl EndorNumber {
CompareOp::Eq => order == Some(Ordering::Equal),
CompareOp::Ne => order != Some(Ordering::Equal),
CompareOp::Lt => order == Some(Ordering::Less),
// 小于等于仅接受已知 Less 或 Equal;NaN 造成的无序 None 不满足关系。
CompareOp::Le => matches!(order, Some(Ordering::Less | Ordering::Equal)),
CompareOp::Gt => order == Some(Ordering::Greater),
// 大于等于仅接受已知 Greater 或 Equal,无序值同样返回 false。
CompareOp::Ge => matches!(order, Some(Ordering::Greater | Ordering::Equal)),
})
}
+8
View File
@@ -1604,21 +1604,29 @@ impl ObjectStore {
AnnotationType::Any => true,
AnnotationType::Unit => matches!(value, Value::Nil),
AnnotationType::Number => {
// Number 同时接受直接数值;另一路只认 EndorNumberObject 载荷,不把任意名义类当数值。
matches!(value, Value::Number(_))
// 对象句柄已在入口验证存活,仍须下转到具体数值载荷以匹配 Number 类别。
|| matches!(value,Value::Object(object) if self.object(*object)?.payload().as_any().is::<crate::EndorNumberObject>())
}
AnnotationType::String => {
// String 同时接受直接 Text;对象表示只接受内置字符串载荷。
matches!(value, Value::Text(_))
// 对象必须确为 EndorStringObject,不通过 str 协议把任意对象隐式转文本。
|| matches!(value,Value::Object(object) if self.object(*object)?.payload().as_any().is::<crate::EndorStringObject>())
}
// Bool 仅匹配直接布尔值,不用真值判断把其他对象归成 Bool。
AnnotationType::Bool => matches!(value, Value::Bool(_)),
AnnotationType::Function => {
// Function 看载荷的函数分类标志;任意可调用 Type 不自动属于 Function。
matches!(value,Value::Object(object) if self.object(*object)?.payload().is_function())
}
AnnotationType::Type => {
// Type 要求实际类型定义载荷,不按对象名或是否可调用猜测。
matches!(value,Value::Object(object) if self.object(*object)?.payload().as_any().is::<EndorTypeObject>())
}
AnnotationType::Trait => {
// Trait 要求实际能力定义载荷,与实现某能力的普通实例不同。
matches!(value,Value::Object(object) if self.object(*object)?.payload().as_any().is::<EndorTraitObject>())
}
AnnotationType::Nominal(definition) => match value {
+6 -1
View File
@@ -210,7 +210,7 @@ fn typed_method_storage_cannot_diverge_from_class_dispatch() {
// 能力声明失败应保持定义版本和声明集合不变。
let version = store.change_version(class).unwrap();
assert!(matches!(
// 存储冲突消除后能力声明才允许提交。
// 当前后端仍有遮蔽字段,能力声明必须在状态提交前失败。
store.implement_trait(class, capability),
Err(VmError::TraitViolation { .. })
));
@@ -236,18 +236,22 @@ fn typed_method_storage_cannot_diverge_from_class_dispatch() {
store.supports(proxy, capability),
Err(VmError::TraitViolation { .. })
));
// 后端已出现遮蔽方法的冲突字段时,即使只读属性查询也必须拒绝不合法的能力存储。
assert!(matches!(
store.get_attr(proxy, "check"),
Err(VmError::TraitViolation { .. })
));
// 绑定方法入口不能绕过同一存储校验,不能先创建可调用绑定再放过冲突。
assert!(matches!(
store.bind_attr(proxy, "check"),
Err(VmError::TraitViolation { .. })
));
// 直接方法契约检查也拒绝代理后端冲突,防止跳过属性查找后绕过门禁。
assert!(matches!(
store.check_method_contract_call(function, proxy, false),
Err(VmError::TraitViolation { .. })
));
// 显式实例存储检查应给出同类能力违规,保持各调用入口的检查一致。
assert!(matches!(
store.check_typed_instance_storage(proxy),
Err(VmError::TraitViolation { .. })
@@ -289,6 +293,7 @@ fn typed_method_storage_cannot_diverge_from_class_dispatch() {
Value::Bool(true)
);
assert!(store.supports(reflective, capability).unwrap());
// 带反射检查的载荷第一次非法写入仍须拒绝,递归防护不能误跳过最外层约束。
assert!(matches!(
store.set_attr(reflective, "check", Value::Nil),
Err(VmError::TraitViolation { .. })
+1
View File
@@ -42,6 +42,7 @@ fn standalone_storage_preserves_identity_validates_edges_and_collects_cycles() {
store.release(cell).unwrap();
assert_eq!(store.collect_cycles(), 2);
assert_eq!(store.object_count(), 0);
// 独立 Store 释放完拥有图后,原 Cell 借用句柄也应失效。
assert!(matches!(store.object(cell), Err(VmError::InvalidHandle)));
other.release(foreign).unwrap();
}