From a7078adc544cb81511fe73987775e9b49b5f5ea9 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Thu, 12 Nov 2026 21:37:03 +0800 Subject: [PATCH 01/62] docs: explain builtin payload ownership and VM consumers --- crates/objects/src/builtins.rs | 108 +++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/crates/objects/src/builtins.rs b/crates/objects/src/builtins.rs index d7323d8..2122185 100644 --- a/crates/objects/src/builtins.rs +++ b/crates/objects/src/builtins.rs @@ -1,3 +1,10 @@ +//! 内置字符串、字典、闭包单元、源函数和异常实例的 Rust 载荷定义。 +//! ObjectStore 的 store_builtins 创建/更新这些载荷;core VM 调用和容器操作通过同一对象存储访问它们。 +//! 这里的构造器只组织 Rust 数据,不分配托管身份、不验证跨堆引用,也不因 Clone 自动 retain ObjectRef。 +//! 分配/替换时由 Heap 按 trace 列出的每一条拥有边验证并计数;trace 必须报告重复引用,不能自行去重。 +//! 载荷 trace 在分配/替换时生成边快照;循环回收读取 EndorObject 保存的快照,而非依赖 Clone 追踪引用。 +//! kind_name 只是诊断标签;语言名义类型由对象头/类型注册表另行管理。 + use std::any::Any; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; @@ -5,56 +12,73 @@ use std::hash::{Hash, Hasher}; use crate::{CompareOp, ObjectPayload, ObjectRef, Value, VmError}; #[derive(Debug, Clone, PartialEq, Eq)] +/// 堆内不可变字符串载荷;宿主可通过 ObjectStore 分配,字符索引按 Unicode 标量而非字节。 pub struct EndorStringObject { + // 自有 UTF-8 文本,不包含托管对象引用;与即时 Value::Text 是不同存储表示。 text: String, } impl EndorStringObject { + /// ObjectStore::new_string 调用,接收可转为 String 的文本并保存;此步尚未进入托管堆。 pub fn new(text: impl Into) -> Self { Self { text: text.into() } } + /// 字典 key_equal 和宿主查询借用 UTF-8 内容;借用不创建托管根,也不修改字符串。 pub fn as_str(&self) -> &str { &self.text } + /// 宿主/内置对象测试查询 Unicode 标量数量;不是 UTF-8 字节数或用户可见字素数。 pub fn char_count(&self) -> usize { self.text.chars().count() } + /// 宿主/内置对象测试按零基 Unicode 标量索引查询;从头线性扫描,越界返回 None。 pub fn char_at(&self, index: usize) -> Option { self.text.chars().nth(index) } } impl ObjectPayload for EndorStringObject { + /// ObjectStore::assign 调用,声明默认 assign_from 不会就地修改载荷;只读阶段可跳过写尝试,允许调用方重绑局部引用。 fn reference_assignment_only(&self) -> bool { true } + /// ObjectStore::payload、内置验证和比较通过此入口向下转换实际 Rust 载荷;不做语言类型判断。 fn as_any(&self) -> &dyn Any { self } + /// ObjectStore 的错误/观察信息调用,返回静态诊断标签,不声明名义类型或继承。 fn kind_name(&self) -> &'static str { "string" } + /// ObjectStore::repr 调用,用 Rust 字符串 Debug 生成带引号/转义的文本;不展开托管对象。 fn repr(&self, _identity: ObjectRef) -> Result { Ok(format!("{:?}", self.text)) } + /// ObjectStore::str 调用,返回原文的独立 String 副本,不带 repr 引号。 fn str(&self, _identity: ObjectRef) -> Result { Ok(self.text.clone()) } + /// VM 条件或宿主真值查询调用;空字符串为假,其余为真,不修剪空白。 fn is_true(&self) -> Result { Ok(!self.text.is_empty()) } + /// ObjectStore::hash/字典键校验调用,按完整文本求哈希;不是稳定跨版本的持久化摘要。 fn hash(&self, _identity: ObjectRef) -> Result { + // 每次从空哈希状态混入同样的文本内容,使相等字符串提供兼容哈希;这里不缓存哈希值。 let mut hasher = DefaultHasher::new(); self.text.hash(&mut hasher); Ok(hasher.finish()) } + /// ObjectStore::compare 调用,仅与同种字符串载荷按 Rust 字符串词典序比较;不按 locale 或字素排序。 fn compare(&self, other: &dyn ObjectPayload, op: CompareOp) -> Result, VmError> { + // 其他载荷返回“不支持”而不是 false,ObjectStore 才能继续尝试反向比较或其错误处理。 let Some(other) = other.as_any().downcast_ref::() else { return Ok(None); }; + // 比较种类由调用方提供;六种操作都比较文本内容,ObjectRef 身份不参与结果。 Ok(Some(match op { CompareOp::Eq => self.text == other.text, CompareOp::Ne => self.text != other.text, @@ -70,47 +94,61 @@ impl ObjectPayload for EndorStringObject { /// key/value reference is validated, retained, traced, and released correctly. /// Linear lookup is intentional for the initial representation layer. #[derive(Debug, Clone, Default)] +/// 按插入顺序拥有键值的字典载荷;查询/写入语义由 store_builtins 统一实施。 pub struct EndorDictObject { + // 按首次插入顺序保存键值对;只允许存储层整体替换载荷,不能绕过 Heap 的拥有边和只读写屏障。 pub(crate) entries: Vec<(Value, Value)>, } impl EndorDictObject { + /// ObjectStore::new_dict/dict_clear 创建空的插入顺序容器,再交 Heap 管理实际身份与拥有边。 pub fn new() -> Self { Self::default() } + /// 容器 repr 和宿主查询获取键值对数量,不是托管引用数;同一对象可在多个项中重复出现。 pub fn len(&self) -> usize { self.entries.len() } + /// 字典真值查询复用此检查;只判断 entries 是否为空,不调用键或值的语言代码。 pub fn is_empty(&self) -> bool { self.entries.is_empty() } + /// ObjectStore::dict_items 借出按插入顺序的切片;键值中的 ObjectRef 仍属借用,Clone 不会增根。 pub fn entries(&self) -> &[(Value, Value)] { &self.entries } } impl ObjectPayload for EndorDictObject { + /// ObjectStore::assign 调用,声明默认 assign_from 不会就地修改载荷;只读阶段可跳过写尝试,允许调用方重绑局部引用。 fn reference_assignment_only(&self) -> bool { true } + /// ObjectStore::payload、内置验证和比较通过此入口向下转换实际 Rust 载荷;不做语言类型判断。 fn as_any(&self) -> &dyn Any { self } + /// ObjectStore 的错误/观察信息调用,返回静态诊断标签,不声明名义类型或继承。 fn kind_name(&self) -> &'static str { "dict" } + /// Heap 分配/替换时收集拥有边,循环回收再使用保存的边快照;键和值中的每个 ObjectRef 都须报告,包括重复引用。 fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { + // 键和值分别计一条边;同一对象同时作键和值时必须报告两次。 for (key, value) in &self.entries { trace_value(key, visit); trace_value(value, visit); } } + /// ObjectStore::repr 查询仅显示容器长度,不递归格式化键值,避免循环图导致无限展开。 fn repr(&self, _identity: ObjectRef) -> Result { Ok(format!("", self.len())) } + /// ObjectStore::is_true 查询字典是否非空;内容可变,所以每次取当前长度。 fn is_true(&self) -> Result { Ok(!self.is_empty()) } + /// 字典键校验/ObjectStore::hash 调用时明确拒绝;可变容器没有内容稳定的键哈希契约。 fn hash(&self, _identity: ObjectRef) -> Result { Err(VmError::UnsupportedOperation("dict hash")) } @@ -118,35 +156,46 @@ impl ObjectPayload for EndorDictObject { /// An empty cell differs from a cell containing Value::Nil. #[derive(Debug, Clone, Default)] +/// 闭包共享单元载荷;VM 的局部捕获和 free-variable 操作通过 ObjectStore 更新其内容。 pub struct EndorCellObject { + // None 是未初始化单元;Some(Nil) 是合法已初始化值,二者在 cell_get/字节码闭包读取时必须区分。 pub(crate) value: Option, } impl EndorCellObject { + /// ObjectStore::new_cell/cell_set 创建单元载荷;Option 区分未初始化与已保存 Nil,Heap 才负责引用计数。 pub fn new(value: Option) -> Self { Self { value } } + /// ObjectStore::cell_get 借出已有值;空单元保持 None,由调用方转为 EmptyCell 错误。 pub fn value(&self) -> Option<&Value> { self.value.as_ref() } } impl ObjectPayload for EndorCellObject { + /// ObjectStore::assign 调用,声明默认 assign_from 不会就地修改载荷;只读阶段可跳过写尝试,允许调用方重绑局部引用。 fn reference_assignment_only(&self) -> bool { true } + /// ObjectStore::payload、内置验证和比较通过此入口向下转换实际 Rust 载荷;不做语言类型判断。 fn as_any(&self) -> &dyn Any { self } + /// ObjectStore 的错误/观察信息调用,返回静态诊断标签,不声明名义类型或继承。 fn kind_name(&self) -> &'static str { "cell" } + /// Heap 读取已初始化单元的拥有边;空单元无边,Some(Nil) 也不含对象边。 fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { + // 未初始化单元不持有 Value;已初始化即时值由 trace_value 识别为没有托管边。 if let Some(value) = &self.value { trace_value(value, visit); } } + /// 宿主 repr 只区分 occupied/empty,不把单元值展开,避免递归闭包或单元环。 fn repr(&self, _identity: ObjectRef) -> Result { + // 只看 Option 是否占用,Some(Nil) 与其他已初始化值一样显示 occupied。 Ok(if self.value.is_some() { "" } else { @@ -154,22 +203,31 @@ impl ObjectPayload for EndorCellObject { } .into()) } + /// 字典键校验拒绝把可变 Cell 用作可哈希键,不退化为一个看似内容稳定的哈希值。 fn hash(&self, _identity: ObjectRef) -> Result { Err(VmError::UnsupportedOperation("cell hash")) } } #[derive(Debug, Clone)] +/// 源函数的代码与定义环境载荷;core 根据这些借用访问器建立真实 VM 帧,不由 payload.call 递归解释。 pub struct EndorFunctionObject { + // 源函数显示名,供 repr/诊断使用,不是依此重新查找全局函数的键。 name: String, + // 已加载 EndorCodeObject 的强边;执行器从中读取参数、局部槽和指令。 code: ObjectRef, + // new_function 捕获创建时 globals.__builtins__ 的字典身份;None 表示没有该回退环境。 builtins: Option, + // 调用期间按名称读取的全局字典强边,不复制函数定义时的所有全局值。 globals: ObjectRef, + // 最后若干位置参数的缺省值;对象值由 trace 声明拥有,执行器按缺参位置补齐。 defaults: Vec, + // 与 code.free_variables 顺序一致的 Cell 强边;多个函数可共享同一单元以观察后续写入。 closure: Vec, } impl EndorFunctionObject { + /// 仅 ObjectStore::new_function 创建源函数载荷;代码/字典/闭包形状由存储层验证,字段强边由 trace 声明。 pub(crate) fn new( name: String, code: ObjectRef, @@ -187,80 +245,106 @@ impl EndorFunctionObject { closure, } } + /// 执行器和诊断借用源函数显示名;不按这个字符串重新绑定其他同名函数。 pub fn name(&self) -> &str { &self.name } + /// core 执行器获取代码身份以建立帧;返回 ObjectRef 是借用句柄,不新增外部根。 pub fn code(&self) -> ObjectRef { self.code } + /// core 执行器/注解解析读取创建时捕获的 builtin 回退字典;不在每次调用重取 globals.__builtins__。 pub fn builtins(&self) -> Option { self.builtins } + /// core 执行器/效果分析读取函数自己的全局环境身份;环境内容仍可在后续输入改变。 pub fn globals(&self) -> ObjectRef { self.globals } + /// core 参数准备借用尾部缺省值;省略参数时使用这些实际 Value,不重新执行缺省值表达式。 pub fn defaults(&self) -> &[Value] { &self.defaults } + /// core 建帧按 free-variable 顺序借用 Cell 身份,使兄弟闭包共享实际单元而非复制当前值。 pub fn closure(&self) -> &[ObjectRef] { &self.closure } } impl ObjectPayload for EndorFunctionObject { + /// VM/契约检查调用,明确源函数属于 Function;与可调用但不属 Function 的 Type 对象区分。 fn is_function(&self) -> bool { true } + /// ObjectStore::assign 调用,声明默认 assign_from 不会就地修改载荷;只读阶段可跳过写尝试,允许调用方重绑局部引用。 fn reference_assignment_only(&self) -> bool { true } + /// ObjectStore::payload、内置验证和比较通过此入口向下转换实际 Rust 载荷;不做语言类型判断。 fn as_any(&self) -> &dyn Any { self } + /// ObjectStore 的错误/观察信息调用,返回静态诊断标签,不声明名义类型或继承。 fn kind_name(&self) -> &'static str { "function" } + /// ObjectStore 设置普通实例字段前查询;允许对象头保存属性,不表示载荷私有字段可被任意直接修改。 fn allows_attributes(&self) -> bool { true } + /// Heap 按函数拥有的代码、全局、可选 builtins、Cell 和默认值逐条计数/追踪;同一身份重复出现也要报告。 fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { visit(self.code); visit(self.globals); + // 可选回退环境存在时同样拥有;它与 globals 若为同一对象也不能省略这一次访问。 if let Some(builtins) = self.builtins { visit(builtins); } + // Cell 本身是强边;当前 Cell 的内容由 Cell.trace 另行声明,不在这里跳过单元直接追踪值。 for reference in &self.closure { visit(*reference); } + // 缺省参数中的即时值不含对象边,Object 变体则按每个位置报告。 for value in &self.defaults { trace_value(value, visit); } } + /// ObjectStore::repr 返回函数显示名,不执行函数、不展开其 globals/closure。 fn repr(&self, _identity: ObjectRef) -> Result { Ok(format!("", self.name)) } } #[derive(Debug, Clone, PartialEq, Eq)] +/// 异常构建器接收的纯数据位置记录,没有对代码对象或文件的托管拥有边。 pub struct EndorTracebackEntry { + // 宿主提供的函数显示名和文件来源;这里不保留可执行函数或文件资源句柄。 pub function: String, pub file: String, + // 调用方提供的行位置,本记录不做位置换算或非零验证。 pub line: usize, } /// Common exception instance data, not an exception class or inheritance tree. #[derive(Debug, Clone)] +/// 宿主可分配的公共异常实例数据;本文件不实现 raise/catch 控制流或异常类层次。 pub struct EndorBaseExceptionObject { + // 异常显示名称,不建立同名异常类或继承关系。 name: String, + // 参数可包含即时值或对象;对象参数通过 trace 成为真实托管拥有边。 args: Vec, + // 显式原因与隐式上下文分别保存;存储层要求它们指向异常载荷。 cause: Option, context: Option, + // 仅保存上下文抑制标记;设置此值不会解除 context 的拥有边。 suppress_context: bool, + // 说明和 traceback 是自有文本/位置记录,本载荷不据此读取文件或执行回调。 notes: Vec, traceback: Vec, } impl EndorBaseExceptionObject { + /// 宿主/内置对象测试构造异常实例数据;初始没有 cause/context/notes/traceback,尚未分配或抛出异常。 pub fn new(name: impl Into, args: Vec) -> Self { Self { name: name.into(), @@ -272,71 +356,91 @@ impl EndorBaseExceptionObject { traceback: Vec::new(), } } + /// 分配前的构建器记录显式原因并打开上下文抑制;不在此验证句柄或释放已有 context。 pub fn with_cause(mut self, cause: ObjectRef) -> Self { self.cause = Some(cause); self.suppress_context = true; self } + /// 分配前的构建器记录上下文强边;不自动改变 suppress_context,也不执行上下文异常。 pub fn with_context(mut self, context: ObjectRef) -> Self { self.context = Some(context); self } + /// 宿主构建器覆盖抑制标记,仍保留 context 引用,显示标记与 GC 可达关系分开。 pub fn suppress_context(mut self, suppress: bool) -> Self { self.suppress_context = suppress; self } + /// 宿主构建器追加自有说明文本,保留调用顺序,不做换行/长度校验或输出。 pub fn with_note(mut self, note: impl Into) -> Self { self.notes.push(note.into()); self } + /// 宿主构建器按调用顺序追加位置记录;本方法不遍历 VM 帧,也不验证记录的来源位置。 pub fn with_traceback(mut self, entry: EndorTracebackEntry) -> Self { self.traceback.push(entry); self } + /// 异常查询和格式化借用显示名;名称不是语言 Type 身份。 pub fn name(&self) -> &str { &self.name } + /// 宿主查询借用实际参数切片;其中对象值由异常载荷拥有,复制句柄不自动 retain。 pub fn args(&self) -> &[Value] { &self.args } + /// ObjectStore::validate_builtin/trace 读取可选显式原因;返回借用身份,不创建新拥有根。 pub fn cause(&self) -> Option { self.cause } + /// ObjectStore::validate_builtin/trace 读取可选上下文;即使已抑制显示,此强边仍参与追踪。 pub fn context(&self) -> Option { self.context } + /// 宿主显示/测试读取标志;本载荷的 repr/str 不据此展开异常链。 pub fn context_suppressed(&self) -> bool { self.suppress_context } + /// 宿主查询按追加顺序借用说明;repr/str 当前不会自动把 notes 拼入输出。 pub fn notes(&self) -> &[String] { &self.notes } + /// 宿主查询借用显式位置记录;不因读取此切片而捕获当前 VM 调用栈。 pub fn traceback(&self) -> &[EndorTracebackEntry] { &self.traceback } } impl ObjectPayload for EndorBaseExceptionObject { + /// ObjectStore::assign 调用,声明默认 assign_from 不会就地修改载荷;只读阶段可跳过写尝试,允许调用方重绑局部引用。 fn reference_assignment_only(&self) -> bool { true } + /// ObjectStore::payload、内置验证和比较通过此入口向下转换实际 Rust 载荷;不做语言类型判断。 fn as_any(&self) -> &dyn Any { self } + /// ObjectStore 的错误/观察信息调用,返回静态诊断标签,不声明名义类型或继承。 fn kind_name(&self) -> &'static str { "exception" } + /// ObjectStore 设置普通实例字段前查询;允许对象头保存属性,不表示载荷私有字段可被任意直接修改。 fn allows_attributes(&self) -> bool { true } + /// Heap 追踪参数对象与两种异常链边;suppress_context 仅是显示数据,不得让 GC 漏掉上下文。 fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { + // 参数按原顺序逐个报告;String/Number 等即时值由统一助手跳过。 for value in &self.args { trace_value(value, visit); } + // flatten 仅跳过 None,不去重;两种链指向同一异常时仍是两条真实拥有边。 for reference in [self.cause, self.context].into_iter().flatten() { visit(reference); } } + /// ObjectStore::repr 组合名称和参数的 Rust Debug 文本;不递归调用参数对象的语言 repr。 fn repr(&self, _identity: ObjectRef) -> Result { Ok(format!( "{}({})", @@ -348,7 +452,9 @@ impl ObjectPayload for EndorBaseExceptionObject { .join(", ") )) } + /// ObjectStore::str 按参数形状生成消息;不自动拼接 cause/context、notes 或 traceback。 fn str(&self, _identity: ObjectRef) -> Result { + // 零参数为空消息;单 Text/Number 用其内容;其他形状(含单 Object)保留 Rust Debug 形式。 Ok(match self.args.as_slice() { [] => String::new(), [Value::Text(text)] => text.clone(), @@ -358,7 +464,9 @@ impl ObjectPayload for EndorBaseExceptionObject { } } +/// 内置载荷及其他对象的 trace 共用助手:只把 Value::Object 声明为拥有边,不 retain/release 或递归遍历子图。 pub(crate) fn trace_value(value: &Value, visit: &mut dyn FnMut(ObjectRef)) { + // Heap 的外层遍历负责后续子图;这里只有一层变体检查,即时值无需 GC 边。 if let Value::Object(reference) = value { visit(*reference); } -- 2.52.0 From 523ab6c745e68727973e7b0be69c1405876a9c4d Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Thu, 12 Nov 2026 21:42:24 +0800 Subject: [PATCH 02/62] docs(objects): explain builtin store operations and ownership --- crates/objects/src/store_builtins.rs | 79 ++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 5 deletions(-) diff --git a/crates/objects/src/store_builtins.rs b/crates/objects/src/store_builtins.rs index d6ddcf8..13702ce 100644 --- a/crates/objects/src/store_builtins.rs +++ b/crates/objects/src/store_builtins.rs @@ -1,3 +1,8 @@ +//! ObjectStore 的内置载荷入口;core::Vm 的同名方法把创建和访问委托到这里。 +//! eval 使用字典实现全局命名空间,使用 Cell 让多个闭包共享可更新的捕获槽。 +//! 返回句柄的 new_* 经 allocate 建立一份外部强引用;读取时复制 Value/ObjectRef +//! 不会自动 retain。修改已有容器统一经 Heap::replace_payload 更新强边与变更记录。 + use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; @@ -8,11 +13,16 @@ use crate::{ }; impl ObjectStore { + /// allocate 与 ObjectStore::replace_payload 在写入前调用,检查特定内置载荷的结构约束。 + /// 这里只检查函数、异常的特定关系;通用引用边的有效性仍由 Heap 的 trace 路径检查。 pub(crate) fn validate_builtin(&self, payload: &dyn ObjectPayload) -> Result<(), VmError> { + // 源码函数载荷必须引用代码对象,默认值和捕获槽必须与该代码的布局一致。 if let Some(function) = payload.as_any().downcast_ref::() { + // 借用代码元数据,不执行指令,也不为该借用新增引用计数。 let code = self .payload::(function.code(), "code")? .code(); + // 默认值可以只覆盖末尾部分参数;闭包槽数量则必须与自由变量数量完全一致。 if function.defaults().len() > code.parameters().len() || function.closure().len() != code.free_variables().len() { @@ -20,15 +30,20 @@ impl ObjectStore { "function defaults or closure do not match code", )); } + // 全局空间及可选的内置空间都必须是字典;无需读取它们的全部键值内容。 self.dictionary_kind(function.globals())?; + // 只有创建时捕获了内置空间才检查;None 不要求补建默认字典。 if let Some(builtins) = function.builtins() { self.dictionary_kind(builtins)?; } + // 每个捕获引用必须指向 Cell;槽的内容可以暂时为空,稍后再由执行器写入。 for cell in function.closure() { self.payload::(*cell, "cell")?; } } + // 异常链只接受异常载荷,避免后续格式化或异常传播拿到其他对象种类。 if let Some(exception) = payload.as_any().downcast_ref::() { + // flatten 跳过不存在的 cause/context,存在的两条边逐一检查。 for reference in [exception.cause(), exception.context()] .into_iter() .flatten() @@ -39,6 +54,8 @@ impl ObjectStore { Ok(()) } + /// VM、内置访问器和宿主通过此入口借用具体 Rust 载荷;expected 仅用于错误提示。 + /// object 会验证句柄并记录对象读取;这里的 Rust downcast 不等同于语言层名义类型判断。 pub fn payload( &self, reference: ObjectRef, @@ -51,14 +68,18 @@ impl ObjectStore { .ok_or(VmError::ObjectKindMismatch(expected)) } + /// Vm::new_string 委托的堆字符串构造入口;文本存入不可变字符串载荷。 pub fn new_string(&mut self, text: impl Into) -> Result { self.allocate(EndorStringObject::new(text)) } + /// Vm::new_code 将已构造的指令/元数据封装为代码对象;此处不启动执行。 pub fn new_code(&mut self, code: EndorCode) -> Result { self.allocate(EndorCodeObject::new(code)) } + /// Vm 的执行入口及 eval 创建闭包时调用,组合代码、全局空间、默认值和捕获槽。 + /// name 用作函数标识;code 指向可执行代码;closure 按该代码的自由变量顺序排列。 pub fn new_function( &mut self, name: impl Into, @@ -67,15 +88,20 @@ impl ObjectStore { defaults: Vec, closure: Vec, ) -> Result { + // 先检查全局空间,再读取它在函数创建时指定的内置命名空间。 self.dictionary_kind(globals)?; + // 保存的是字典身份:字典内部后续更新可见,但改写 globals.__builtins__ 不会重绑定此字段。 let builtins = match self.dict_get_name(globals, "__builtins__")? { + // 有该键时必须是字典对象;普通对象或标量(包括 Nil)都不能充当内置空间。 Some(Value::Object(reference)) => { self.dictionary_kind(reference)?; Some(reference) } + // 缺少键表示无内置空间,与显式存放 Nil 不同。 None => None, _ => return Err(VmError::ObjectKindMismatch("dict")), }; + // allocate 再校验代码/槽布局,并由 trace 为这些字段引用的对象建立内部强边。 self.allocate(EndorFunctionObject::new( name.into(), code, @@ -86,25 +112,30 @@ impl ObjectStore { )) } + /// Vm::new_number 构造堆上的数值对象,与 Value::Number 的直接数值表示分开。 pub fn new_number(&mut self, number: EndorNumber) -> Result { self.allocate(EndorNumberObject::new(number)) } + /// Vm::new_dict 及 eval 的字典构造路径使用,初始没有条目或内部引用边。 pub fn new_dict(&mut self) -> Result { self.allocate(EndorDictObject::new()) } - /// Returns borrowed keys and values in insertion order. + /// Vm::dict_items 和宿主迭代入口按插入顺序借用条目;不复制容器,也不新增外部强引用。 + /// payload 路径记录整个对象的读取,因此迭代依赖整个字典内容。 pub fn dict_items(&self, dictionary: ObjectRef) -> Result<&[(Value, Value)], VmError> { Ok(self .payload::(dictionary, "dict")? .entries()) } + /// 函数校验与按名称查找共用的种类检查,只观察“仍是字典”,不观察其全部内容。 pub(crate) fn dictionary_kind( &self, dictionary: ObjectRef, ) -> Result<&EndorDictObject, VmError> { + // 直接走 heap.get:若改用 payload/object,会附带过宽的整对象读取依赖。 let dict = self .heap .get(dictionary)? @@ -112,25 +143,32 @@ impl ObjectStore { .as_any() .downcast_ref::() .ok_or(VmError::ObjectKindMismatch("dict"))?; + // 仅在依赖收集上下文中登记种类观察;有效句柄和 downcast 已在上面检查。 self.heap.record_dictionary_kind(dictionary); Ok(dict) } - /// Name lookup records both present and missing text keys. General dict_get - /// and iteration continue to observe the entire dictionary contents. + /// eval 查找全局名和内置名时调用:既记录命中值,也记录“此名称不存在”。 + /// 这里只查一个字典;全局未命中后再查 builtins 的顺序由 eval 决定。 + /// 通用 dict_get 和条目迭代仍采用整字典依赖。 pub fn dict_get_name( &self, dictionary: ObjectRef, name: &str, ) -> Result, VmError> { + // 先确认字典种类,再以文本键查找,避免无关名称变更污染本次读取的依赖。 let dict = self.dictionary_kind(dictionary)?; + // clone 复制返回的 Value 表示;若其中含对象句柄,它仍是借用句柄。 let value = self .dict_position(dict, &Value::Text(name.into()))? .map(|index| dict.entries[index].1.clone()); + // None 也要登记,使未来新增这个名称能使原先的缺失观察失效。 self.heap.record_name(dictionary, name, &value); Ok(value) } + /// Vm::dict_get 与宿主通用键查找入口;返回 None 表示缺键,Some(Nil) 表示存了空值。 pub fn dict_get(&self, dictionary: ObjectRef, key: &Value) -> Result, VmError> { + // 通用读取保守地观察整个字典;先检查键可哈希,再进行线性相等比较。 let dict = self.payload::(dictionary, "dict")?; self.key_hash(key)?; Ok(self @@ -138,43 +176,58 @@ impl ObjectStore { .map(|index| dict.entries[index].1.clone())) } + /// Vm::dict_set、eval 构造字典和写入全局名时调用;修改保持字典对象身份。 pub fn dict_set( &mut self, dictionary: ObjectRef, key: Value, value: Value, ) -> Result<(), VmError> { + // 先复制载荷准备新状态;Rust clone 不 retain,提交前仍由旧字典持有原有引用边。 let mut dict = self.payload::(dictionary, "dict")?.clone(); self.key_hash(&key)?; + // 对象值必须来自本堆且仍有效;标量无需额外句柄检查。 self.validate_value(&value)?; if let Some(index) = self.dict_position(&dict, &key)? { - // Preserve the first key object and insertion position. + // 相等键只覆盖值,保留第一次插入的键对象及其顺序,不保存本次传入的替代键。 dict.entries[index].1 = value; } else { + // 新键追加到末尾;因此遍历顺序就是首次插入顺序。 dict.entries.push((key, value)); } + // Heap 在实际替换前检查新边和只读屏障,先保留新边再释放旧边,并记录 Contents 变更。 self.heap.replace_payload(dictionary, dict) } + /// Vm::dict_delete 删除一个键;返回值说明是否存在该键,错误通过 Result 单独传播。 pub fn dict_delete(&mut self, dictionary: ObjectRef, key: &Value) -> Result { + // 先复制载荷准备新状态;Rust clone 不 retain,提交前仍由旧字典持有原有引用边。 let mut dict = self.payload::(dictionary, "dict")?.clone(); self.key_hash(key)?; + // 缺键直接返回,不提交替换,因此也不会触发已有对象的写屏障或产生变更记录。 let Some(index) = self.dict_position(&dict, key)? else { return Ok(false); }; + // 删除后其余条目的相对顺序不变;被删除键值的强边在 Heap 提交时才释放。 dict.entries.remove(index); + // Heap 在实际替换前检查新边和只读屏障,先保留新边再释放旧边,并记录 Contents 变更。 self.heap.replace_payload(dictionary, dict)?; Ok(true) } + /// Vm::dict_clear 用空载荷替换条目集合;对象身份保留,原键值的强边由 Heap 释放。 pub fn dict_clear(&mut self, dictionary: ObjectRef) -> Result<(), VmError> { + // 先确认字典种类;即使原本为空仍走替换路径,受相同只读保护约束。 self.payload::(dictionary, "dict")?; self.heap .replace_payload(dictionary, EndorDictObject::new()) } + /// 字典读写共用的线性定位;条目向量没有哈希索引,hash 只负责准入校验。 fn dict_position(&self, dict: &EndorDictObject, key: &Value) -> Result, VmError> { + // 只比较键,忽略值;相等比较失败时立即返回错误,不把失败解释为不相等。 for (index, (existing, _)) in dict.entries.iter().enumerate() { + // 返回第一个相等键的位置,供读值、覆盖值或删除整条记录使用。 if self.key_equal(existing, key)? { return Ok(Some(index)); } @@ -182,23 +235,30 @@ impl ObjectStore { Ok(None) } + /// 通用字典操作检查键是否支持哈希;返回值当前不用于条目定位,也不用于持久化。 fn key_hash(&self, value: &Value) -> Result { + // 对象键先验证归属和存活,随后才允许访问它的载荷哈希实现。 self.validate_value(value)?; + // 直接值使用标准哈希器;对象委托各自实现,可由载荷拒绝不可哈希种类。 let mut hasher = DefaultHasher::new(); match value { Value::Nil => "nil".hash(&mut hasher), Value::Bool(value) => value.hash(&mut hasher), Value::Text(value) => value.hash(&mut hasher), + // 数值直接表示尚未定义字典哈希语义,因此明确拒绝,不隐式转换成文本或整数。 Value::Number(_) => return Err(VmError::UnsupportedOperation("numeric hash")), Value::Object(reference) => return self.hash(*reference), } Ok(hasher.finish()) } + /// dict_position 的键比较:直接文本与堆字符串按内容互通,其余按对象协议或 Value 比较。 fn key_equal(&self, left: &Value, right: &Value) -> Result { + // 将两种字符串表示临时取成拥有的 String;其他对象返回 None,仍保留后续比较路径。 let text = |value: &Value| -> Result, VmError> { Ok(match value { Value::Text(text) => Some(text.clone()), + // 借用载荷尝试字符串 downcast;非字符串不是错误,无效句柄才会传播错误。 Value::Object(reference) => self .object(*reference)? .payload() @@ -208,9 +268,11 @@ impl ObjectStore { _ => None, }) }; + // 两边都是文本才做内容相等;不做 Unicode 规范化或大小写折叠。 if let (Some(left), Some(right)) = (text(left)?, text(right)?) { return Ok(left == right); } + // 两个对象走 Store::compare 的正向/反向协议及身份回退;其他组合使用 Value 的相等实现。 match (left, right) { (Value::Object(left), Value::Object(right)) => { self.compare(*left, *right, CompareOp::Eq) @@ -219,11 +281,13 @@ impl ObjectStore { } } + /// Vm::new_cell 在 eval 把局部槽提升为闭包共享槽时调用;None 表示尚未初始化。 pub fn new_cell(&mut self, value: Option) -> Result { self.allocate(EndorCellObject::new(value)) } - /// Reads a borrowed value; an empty cell is not Value::Nil. + /// Vm::cell_get 供自由变量读取及赋值前读取旧值使用;复制出的对象句柄仍是借用。 + /// 空槽返回 EmptyCell;已初始化为 Some(Value::Nil) 则正常返回 Nil。 pub fn cell_get(&self, cell: ObjectRef) -> Result { self.payload::(cell, "cell")? .value() @@ -231,13 +295,18 @@ impl ObjectStore { .ok_or(VmError::EmptyCell) } + /// Vm::cell_set 在自由变量赋值时调用;所有共享该 Cell 的闭包随后读取相同的新值。 pub fn cell_set(&mut self, cell: ObjectRef, value: Value) -> Result<(), VmError> { + // 确认是 Cell 后由 Heap 检查引用边和只读屏障,更新时保留 Cell 身份。 self.payload::(cell, "cell")?; self.heap .replace_payload(cell, EndorCellObject::new(Some(value))) } + /// Vm::cell_clear 在删除捕获变量时调用;恢复未初始化状态,并释放旧值的内部强边。 + /// 即使槽原本为空仍提交替换,不能通过重复清空绕过只读屏障。 pub fn cell_clear(&mut self, cell: ObjectRef) -> Result<(), VmError> { + // 确认是 Cell 后由 Heap 检查引用边和只读屏障,更新时保留 Cell 身份。 self.payload::(cell, "cell")?; self.heap.replace_payload(cell, EndorCellObject::default()) } -- 2.52.0 From 6be33467c829fc25e6dc48f379406072dca1c7d9 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Thu, 12 Nov 2026 21:48:14 +0800 Subject: [PATCH 03/62] docs(objects): explain store lifecycle and payload protocols --- crates/objects/src/object.rs | 164 ++++++++++++++++++++++++----------- crates/objects/src/store.rs | 129 +++++++++++++++++++++------ 2 files changed, 217 insertions(+), 76 deletions(-) diff --git a/crates/objects/src/object.rs b/crates/objects/src/object.rs index e1103dc..f73bab2 100644 --- a/crates/objects/src/object.rs +++ b/crates/objects/src/object.rs @@ -1,3 +1,6 @@ +//! 对象公共表示与宿主载荷协议;Heap 管生命周期,ObjectStore 调协议,core::Vm 管指令与调用帧。 +//! Value 的 Rust clone 只复制表示,不变更托管引用计数;所有权由产生/接收它的 API 约定决定。 + use std::any::Any; use std::collections::BTreeMap; use std::collections::hash_map::DefaultHasher; @@ -6,31 +9,43 @@ use std::hash::{Hash, Hasher}; use crate::{EndorNumber, ObjectRef, VmError}; -/// Immediate values stay outside the object heap. Copying an ObjectRef or -/// cloning a Value never retains a reference; ownership follows the API that -/// produced it. In particular, call/execute results own a root until released. +/// 直接值不占对象堆槽;复制 ObjectRef 或 clone Value 不自动保留引用,所有权以 API 为准。 +/// 特别是 VM 的 call/execute 返回对象时带有一份外部根,使用者需要归还。 #[derive(Debug, Clone, PartialEq)] pub enum Value { + // 语言空值,与 Cell 的未初始化 None 不同。 Nil, + // 直接布尔值,不占用对象槽。 Bool(bool), - /// Rust equality preserves representation; it is not numeric equivalence. + /// 直接数值;Rust 相等比较保留具体数值表示,不等于语言层数值等价判断。 Number(EndorNumber), + // 拥有文本的直接表示,与堆上的 EndorStringObject 可在字典键比较中按内容互通。 Text(String), + // 仅保存句柄;是否拥有外部根取决于 API,复制/丢弃此变体不会 retain/release。 Object(ObjectRef), } +/// VM 的比较运算标识;ObjectStore::compare 传入载荷,必要时再尝试反向运算。 #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CompareOp { + /// 相等;传给载荷解释,不直接使用 Rust 的对象地址比较。 Eq, + /// 不等;传给载荷解释,不直接使用 Rust 的对象地址比较。 Ne, + /// 小于;传给载荷解释,不直接使用 Rust 的对象地址比较。 Lt, + /// 小于等于;传给载荷解释,不直接使用 Rust 的对象地址比较。 Le, + /// 大于;传给载荷解释,不直接使用 Rust 的对象地址比较。 Gt, + /// 大于等于;传给载荷解释,不直接使用 Rust 的对象地址比较。 Ge, } impl CompareOp { + /// Store 对调左右载荷时使用;相等类不变,有序运算反转方向。 pub(crate) fn reflected(self) -> Self { + // 运算映射只改变方向,不执行比较,也不取反比较结果。 match self { Self::Eq => Self::Eq, Self::Ne => Self::Ne, @@ -42,82 +57,88 @@ impl CompareOp { } } -/// Non-blocking awaitable observation. Ready object values are borrowed from -/// the managed graph and must be retained by the consumer before dropping it. +/// eval 的非阻塞等待观察结果;Ready 中的对象仍借自托管图,消费方须在释放原图前保留结果。 #[derive(Debug)] pub enum AwaitPoll { + // 结果尚未就绪;执行器保存等待状态,之后再观察。 Pending, + // 已就绪的借用结果;消费方在释放原结果图之前须保留所需对象根。 Ready(Value), } -/// Rust-side operation interface, not a language type or metaclass. -/// -/// Implementations must report every payload-owned object reference in trace(), -/// including duplicates. Those edges must stay immutable after allocation; -/// mutable object references belong in VM-managed attributes or built-in -/// containers updated transactionally by the VM instead. -/// Rust Drop releases host resources when the payload is destroyed. -/// Equal payloads must provide compatible hashes, or reject hashing explicitly. +/// 宿主实现的 Rust 操作协议,不是语言层类型或元类;Store/VM 根据具体操作调用这些方法。 +/// trace 必须报告载荷拥有的每一条对象引用(包括重复边),分配后这些边不能私自改变。 +/// 可变托管引用应放入受控属性或内置容器,经 Store/Heap 更新;Rust Drop 负责释放宿主资源。 +/// 内容相等的载荷必须提供兼容哈希,或明确拒绝哈希。 pub trait ObjectPayload: Any + fmt::Debug + Send + Sync { + /// eval 的 await 路径轮询载荷;默认拒绝等待,宿主实现不得阻塞执行线程等待完成。 fn poll_await(&self) -> Result { Err(VmError::UnsupportedOperation("await")) } - /// Explicit function category; callable Type objects are not functions. + /// allocate 判定是否自动附着 Function 类型;可调用 Type 并不属于函数分类。 fn is_function(&self) -> bool { false } - /// Ordinary field storage may be indirect, independently of type identity. - /// Targets must be traced; retargeting records Contents on the forwarder. + /// Store::attribute_path 与类型属性协议查询转发目标;None 表示在自身存储属性。 + /// 目标须由托管强边持有;更换目标须在转发对象上记录 Contents,属性身份与类型身份分开。 fn attribute_target(&self, _store: &crate::ObjectStore) -> Result, VmError> { Ok(None) } - /// Shared-reference facades may expose the current referent's type. The - /// referent must also be held by traced managed edges. Ordinary objects - /// keep their immutable header type; this does not change inheritance. + /// types 的类型解析查询共享引用门面的当前目标;None 表示使用自身头部名义类型。 + /// 目标须有 trace 强边;此协议不修改普通对象不可变的头部类型或继承关系。 fn type_target(&self, _store: &crate::ObjectStore) -> Result, VmError> { Ok(None) } - /// Optional context-aware native entry. The VM clones this dispatcher before - /// borrowing the store mutably; callbacks never borrow a live payload. + /// VM 查找可访问 NativeContext 的原生分派器;先克隆 Arc,再可变借用 Store 执行。 + /// 回调期间不保留载荷借用,避免同时借用载荷和可变 Store。 fn native_callable(&self) -> Option> { None } + /// Store::payload 等借助 Any 下转型到具体 Rust 载荷;不代表语言层类型判断。 fn as_any(&self) -> &dyn Any; - /// A diagnostic label only, with no language type or inheritance semantics. + /// 错误/默认 repr 的诊断名称;不能据此判断语言名义类型、继承或能力。 fn kind_name(&self) -> &'static str; + /// Heap 在分配/替换载荷时收集内部强边(含重复边);GC 此后使用边快照。 + /// 默认无边;实现不能在快照建立后通过内部可变性悄悄改变这些引用。 fn trace(&self, _visit: &mut dyn FnMut(ObjectRef)) {} + /// Heap 属性写入检查是否允许动态字段;默认不允许,Record 显式开启。 fn allows_attributes(&self) -> bool { false } + /// Store::repr 的默认诊断表示包含载荷名称与对象身份,不递归展开整个对象图。 fn repr(&self, identity: ObjectRef) -> Result { Ok(format!("<{} {identity}>", self.kind_name())) } + /// Store::str 的文本协议;未覆盖时复用 repr,宿主可提供更适合显示的文本。 fn str(&self, identity: ObjectRef) -> Result { self.repr(identity) } + /// Store::is_true/VM 分支求真时调用;普通对象默认真,容器或标量载荷可覆盖。 fn is_true(&self) -> Result { Ok(true) } + /// Store::hash 默认按身份哈希;若实现内容相等,须同步实现兼容哈希或拒绝哈希。 fn hash(&self, identity: ObjectRef) -> Result { + // 哈希整个句柄身份,不使用载荷地址;结果不是跨进程稳定标识。 let mut hasher = DefaultHasher::new(); identity.hash(&mut hasher); Ok(hasher.finish()) } - /// None means "not implemented"; the VM tries the reflected operation, - /// then falls back to identity for Eq/Ne or rejects ordering. + /// Store::compare 调用;None 表示未实现,Some(false) 表示已经实现且不满足比较。 + /// Store 仅在 None 时尝试反向协议,最后相等回退身份、有序比较报错。 fn compare( &self, _other: &dyn ObjectPayload, @@ -126,42 +147,39 @@ pub trait ObjectPayload: Any + fmt::Debug + Send + Sync { Ok(None) } - /// Host-call entry: arguments and raw results are borrowed handles. The VM - /// validates/retains the result before exposing it as an owned call result. - /// Language functions are dispatched by Vm's frame evaluator instead. - /// Positional metadata for native call protocols; source code is queried by the store. + /// VM 原生调用协议查询位置参数/返回约束;默认从上下文分派器获取。 + /// 源码函数签名由 Store 查询代码对象,此方法不是执行调用的入口。 fn call_signature(&self) -> Option { self.native_callable() .and_then(|callable| callable.signature()) } + /// VM 查询参数数量限制;默认从上下文分派器获取,None 表示没有此处的数量元数据。 fn call_arity(&self) -> Option { self.native_callable().and_then(|callable| callable.arity()) } + /// VM 的无上下文宿主调用入口:参数及原始结果句柄是借用,VM 随后验证并保留结果根。 + /// 默认不可调用;源码函数另走执行帧,需访问 Store 的原生函数另走 native_callable。 fn call(&self, _arguments: &[Value]) -> Result { Err(VmError::UnsupportedOperation("call")) } - /// Opt-in shared-reference assignment. Returning true preserves the existing - /// object identity after replacing its host-owned target. Ordinary payloads - /// return false and retain normal value assignment. Implementations must - /// validate before mutation and must not add untraced managed-object edges. - /// True only when legacy assign_from always returns false. Read-only - /// callers can then rebind a local reference without attempting mutation. - /// assignment_cell is checked first and remains a protected write protocol. + /// Store::assign_from 的只读优化标志:true 承诺旧式 assign_from 总返回 false。 + /// 因而执行器可在只读调用中直接重绑定局部引用,不尝试修改旧载荷。 + /// 托管 assignment_cell 协议优先且仍受写屏障保护;此标志不赋予修改旧对象的权限。 fn reference_assignment_only(&self) -> bool { false } + /// Store 的旧式宿主原地赋值协议;true 保留目标身份,false 让执行器使用新引用。 + /// 实现须先校验再修改,且不能改变未重新登记的托管强边;默认不原地赋值。 fn assign_from(&self, _source: &dyn ObjectPayload) -> Result { Ok(false) } - /// Optional managed shared-reference assignment. Return a built-in Cell - /// owned by this payload after validating the source and any domain cycles. - /// The store writes the source object to the Cell transactionally. This - /// keeps mutable edges inside the GC graph, unlike native assign_from. + /// Store 优先采用的共享引用赋值协议:校验 source 与领域环路后返回本载荷持有的 Cell。 + /// Store 将 source 写入该 Cell 并记录变更,确保可变引用边始终由 GC 管理;None 表示不用此协议。 fn assignment_cell( &self, _source: ObjectRef, @@ -171,39 +189,53 @@ pub trait ObjectPayload: Any + fmt::Debug + Send + Sync { } } +/// Store::new_object 和普通名义实例的无额外载荷记录;字段保存在 EndorObject.attributes。 #[derive(Debug, Default)] pub struct Record; impl ObjectPayload for Record { + // Record 没有原地赋值实现,允许只读调用直接重绑定引用。 fn reference_assignment_only(&self) -> bool { true } + // Store 下转型入口,返回自身的 Rust 类型信息。 fn as_any(&self) -> &dyn Any { self } + // 供错误或默认对象表示使用的诊断名称。 fn kind_name(&self) -> &'static str { "record" } + // 普通记录允许 Heap 在对象公共属性表中写入动态字段。 fn allows_attributes(&self) -> bool { true } } +/// 无捕获函数指针型宿主回调;含状态/上下文的调用应使用 NativeCallable 协议。 pub type EndorNativeCall = fn(&[Value]) -> Result; +/// 保留给既有嵌入方的回调类型别名。 pub type NativeCall = EndorNativeCall; +/// 宿主把简单 Rust 回调包装成可分配函数载荷,供 VM 的原生调用路径分派。 #[derive(Debug)] pub struct EndorNativeFunctionObject { + // 诊断和参数数量错误使用的函数名称。 name: String, + // 无捕获 Rust 回调,不包含额外托管引用边。 function: NativeCall, + // 可选位置参数数量约束,也可由完整 signature 提供。 arity: Option, + // 完整签名可能引用托管类型,必须通过 trace 持有这些引用。 signature: Option, } impl EndorNativeFunctionObject { + /// 宿主构造尚未分配的载荷;初始没有数量或类型签名约束,之后由 Store::allocate 接管。 pub fn new(name: impl Into, function: NativeCall) -> Self { + // 名称转为拥有的 String;函数指针本身无需额外所有权管理。 Self { name: name.into(), function, @@ -212,56 +244,71 @@ impl EndorNativeFunctionObject { } } + /// 宿主/诊断借用函数名,不执行回调。 pub fn name(&self) -> &str { &self.name } + /// 构造阶段补充完整签名,并同步数量限制;分配后签名中的引用由 Heap 边快照持有。 pub fn with_signature(mut self, signature: crate::CallSignature) -> Self { self.arity = Some(signature.arity); self.signature = Some(signature); self } + /// 构造阶段单独设置数量限制;不会重写已有完整签名,调用方应避免配置互相矛盾。 pub fn with_arity(mut self, arity: crate::CallArity) -> Self { self.arity = Some(arity); self } } -/// Compatibility name for existing embedders. +/// 为已有嵌入方保留的函数载荷名称,不引入第二种运行时对象。 pub type NativeFunction = EndorNativeFunctionObject; impl ObjectPayload for EndorNativeFunctionObject { + // 让 Store::allocate 按配置为该原生函数附着 Function 名义类型。 fn is_function(&self) -> bool { true } + // 不实现旧式原地赋值,因此引用重绑定不要求修改本载荷。 fn reference_assignment_only(&self) -> bool { true } + // Store 下转型入口,返回自身的 Rust 类型信息。 fn as_any(&self) -> &dyn Any { self } + // 供错误或默认对象表示使用的诊断名称。 fn kind_name(&self) -> &'static str { "native-function" } + // 用名称生成诊断表示,不调用回调,也不显示内存地址。 fn repr(&self, _identity: ObjectRef) -> Result { Ok(format!("", self.name)) } + // Heap 分配/替换时收集签名持有的类型引用;函数指针和名称不产生托管强边。 fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { + // 无完整签名时没有签名边;references 统一枚举参数与返回约束中的引用。 if let Some(signature) = &self.signature { + // 不去重:每次拥有的引用都要计入 Heap 的强边数量。 for reference in signature.references() { visit(reference); } } } + // VM 查询签名时复制元数据,不因其中句柄的 clone 而增加外部根。 fn call_signature(&self) -> Option { self.signature.clone() } + // VM 查询构造时声明的数量约束,不经上下文分派器。 fn call_arity(&self) -> Option { self.arity } + // VM 分派或宿主直接调用:先检查数量,再将同一参数切片借给 Rust 回调。 fn call(&self, arguments: &[Value]) -> Result { + // 未声明 arity 时不在这里限制数量;声明后错误必须先于回调的任何副作用返回。 if let Some(arity) = self.arity { arity.check(self.name.clone(), arguments.len())?; } @@ -269,77 +316,96 @@ impl ObjectPayload for EndorNativeFunctionObject { } } -/// Common runtime state. Embedders may attach an immutable managed type. +/// Heap 维护的公共头部;宿主只读访问,计数、版本和类型附着由受控 Store 操作更新。 #[derive(Debug)] pub struct ObjectHeader { + // 完整身份含堆、槽位和代际;槽复用后旧身份不能再次访问新对象。 pub(crate) identity: ObjectRef, + // 总强计数 = 外部根 + 对象图内部强边。 pub(crate) strong_refs: usize, + // 宿主持有和 VM 活跃执行槽持有的根数,是环回收的可达性起点。 pub(crate) external_refs: usize, + // 已记录修改的单调版本,用于整对象依赖重验。 pub(crate) change_version: u64, + // 可选名义类型强边,附着后不能通过普通属性改写。 pub(crate) type_object: Option, } impl ObjectHeader { + /// types/宿主读取头部附着的借用类型句柄;共享引用的有效类型仍需走 type_target 协议。 pub fn type_object(&self) -> Option { self.type_object } + /// Store/缓存借用版本快照;此 getter 本身不登记读取。 pub fn change_version(&self) -> u64 { self.change_version } + /// 宿主诊断读取完整句柄,不新建根引用。 pub fn identity(&self) -> ObjectRef { self.identity } + /// Store/测试读取包含内部边的总强计数。 pub fn ref_count(&self) -> usize { self.strong_refs } - /// Host-owned references and live execution roots, excluding heap edges. + /// Heap 环回收及诊断读取外部根数,不包含对象间强边。 pub fn external_ref_count(&self) -> usize { self.external_refs } } -/// Runtime object foundation, analogous in role to CPython's common object -/// structure, without its binary layout. The heap boxes this -/// structure so its address remains stable throughout its lifetime. +/// Heap 中稳定装箱的对象实体,把公共头部、动态载荷、属性和强边快照放在同一基础结构。 +/// 对象存活期间该实体地址稳定;这不保证单独替换的载荷盒地址不变。 #[derive(Debug)] pub struct EndorObject { + // 生命周期、身份、版本和名义类型公共信息。 pub(crate) header: ObjectHeader, + // 动态 Rust 载荷;替换载荷不会移动外层 EndorObject 的稳定盒地址。 pub(crate) payload: Box, + // 受 Heap 写屏障和计数维护的动态属性表,键是普通 Rust 字符串。 pub(crate) attributes: BTreeMap, + // 分配/替换载荷时记录的强边快照,保留重复项供精确计数与回收。 pub(crate) payload_edges: Vec, } impl EndorObject { + /// Store/宿主只读借用公共头部,不提供绕过计数和类型保护的可变入口。 pub fn header(&self) -> &ObjectHeader { &self.header } + /// Store/宿主借用动态载荷执行协议或下转型;getter 自身不验证句柄或登记读取。 pub fn payload(&self) -> &dyn ObjectPayload { self.payload.as_ref() } - /// Visits owned edges, not external roots. Payload edges are snapshotted at - /// allocation and refreshed on VM-managed payload replacement; attributes - /// are maintained by the VM's reference accounting. + /// Heap 的回收/释放路径枚举内部拥有边,不含外部根;类型、载荷快照和属性各自计数。 + /// 不重新调用载荷 trace,避免内部可变性使 GC 所见图与引用计数脱节。 pub fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { + // 类型附着本身拥有一条强边。 if let Some(reference) = self.header.type_object { visit(reference); } + // 逐项输出载荷快照,重复引用不能合并。 for reference in &self.payload_edges { visit(*reference); } + // 属性只有 Object 变体贡献托管边;文本/数值等由 Value 自身持有。 for value in self.attributes.values() { + // 将属性拥有的对象引用交给访问器,不改变计数。 if let Value::Object(reference) = value { visit(*reference); } } } + /// Heap 需要拥有的边列表时使用,把 trace 的回调输出收集成 Vec,不新增引用根。 pub(crate) fn edges(&self) -> Vec { + // 这是身份列表快照,不是深拷贝对象图。 let mut edges = Vec::new(); self.trace(&mut |reference| edges.push(reference)); edges diff --git a/crates/objects/src/store.rs b/crates/objects/src/store.rs index 359fd2d..5e078e4 100644 --- a/crates/objects/src/store.rs +++ b/crates/objects/src/store.rs @@ -1,20 +1,27 @@ +//! VM 与宿主共用的对象存储门面:分配、根引用、载荷操作、属性路径和读取依赖都从这里进入。 +//! heap 负责底层句柄/计数/写屏障;types 与 store_builtins 扩展此 impl,分别提供类型协议和容器操作。 + use crate::heap::Heap; use crate::{ ChangeBatch, ChangeKey, CompareOp, EndorObject, ObjectPayload, ObjectRef, Record, Value, VmError, WeakObjectRef, }; -/// Object storage independent of the VM evaluator. Construction creates an empty -/// heap; dropping it releases all objects and their host resources. Mutable -/// access is limited to operations that validate and account for owned edges. +/// 独立于指令执行器的存储;Vm 持有它,宿主也可单独使用。构造时堆为空,Drop 时释放全部载荷。 +/// 修改必须经受控入口,使引用边、只读保护和变更记录保持一致。 #[derive(Debug)] pub struct ObjectStore { + // 实际对象槽、引用计数、读依赖和待取变更批次的唯一存储。 pub(crate) heap: Heap, + // 新建函数自动附着的名义类型;使用弱引用,避免配置本身延长类型寿命。 function_type: Option, + // 宿主显式开启的字段声明/替换检查开关;默认关闭。 pub(crate) field_type_checks: bool, + // types 的 RAII guard 按线程登记正在校验的对象,阻止属性/类型转发导致递归重入。 pub(crate) active_attribute_checks: std::sync::Mutex< std::collections::HashMap>, >, + // contracts 按线程记录正在核对的对象/能力对,处理递归契约检查时的重入。 pub(crate) active_trait_checks: std::sync::Mutex< std::collections::HashMap< std::thread::ThreadId, @@ -24,8 +31,9 @@ pub struct ObjectStore { } impl ObjectStore { - /// Opt-in declared-field and method substitution checks, shared with the VM. + /// Vm::set_source_type_checks 委托的配置入口;影响后续字段与方法替换检查,不遍历重验已有对象。 pub fn set_field_type_checks(&mut self, enabled: bool) -> Result<(), VmError> { + // 配置属于持久状态,不能由只读调用改变;即使传入相同配置也拒绝。 if self.is_read_only() { return Err(VmError::UnsupportedOperation( "configure field type checks during read-only call", @@ -35,18 +43,23 @@ impl ObjectStore { Ok(()) } - /// Existing objects are immutable; fresh result graphs may be constructed. - /// Scopes must end in LIFO order. Read handles are borrowed, never GC roots. + /// VM 的只读调用路径进入此作用域:进入前已有对象被冻结,新建结果图仍可构造。 + /// 返回嵌套深度令牌,结束必须按 LIFO;读集合中的句柄是借用,不是 GC 根。 pub fn begin_read_only(&mut self) -> usize { self.heap.begin_read_only() } + /// VM 结束只读调用,返回兼容的对象读取集合;令牌错误时保持作用域未弹出。 pub fn end_read_only(&mut self, scope: usize) -> Result, VmError> { self.heap.end_read_only(scope) } + /// 只读调用的精确版本,额外保留整对象、字典种类、指定名称等不同依赖。 pub fn end_read_only_precise(&mut self, scope: usize) -> Result { self.heap.end_read_only_precise(scope) } + /// 缓存使用者重验一次观察;失效句柄或当前读取失败都表示依赖不再有效。 + /// 经正常读取入口比较,因此若当前另有读取作用域,也会登记相应观察。 pub fn read_dependency_valid(&self, dependency: &crate::ReadDependency) -> bool { + // 整对象检查版本;种类只检查仍是字典;名称检查当前 Option 是否保持相等。 match dependency { crate::ReadDependency::Object { object, version } => { self.change_version(*object).ok() == Some(*version) @@ -63,21 +76,21 @@ impl ObjectStore { .is_ok_and(|current| current == *value), } } + /// VM 子调用结束读取区间,同时取回程序/验证分类以及精确依赖;不结束外层只读作用域。 pub fn end_read_tracking_precise(&self, token: usize) -> Result { self.heap.end_read_tracking_precise(token) } - /// Track one subcall inside the active read-only scope. Regions nest in - /// LIFO order and do not clear the enclosing call's read dependencies. - /// Handles returned by end_read_tracking are borrowed, never owning roots. + /// VM 在已有只读作用域内标记一次子调用;区间按 LIFO 嵌套,不清除外层已收集的依赖。 + /// 没有只读作用域会报错;结束区间返回的句柄不新增根引用。 pub fn begin_read_tracking(&self) -> Result { self.heap.begin_read_tracking() } + /// 兼容入口:结束子区间,只返回读取对象,不保留读取分类和具体依赖键。 pub fn end_read_tracking(&self, token: usize) -> Result, VmError> { self.heap.end_read_tracking(token) } - /// Like end_read_tracking, retaining the distinction between program reads - /// and VM annotation validation. Ordinary reads always dominate. + /// VM 核对调用契约时使用,区分程序内容读取与注解验证读取;两者都有时内容读取优先。 pub fn end_read_tracking_details( &self, token: usize, @@ -85,18 +98,20 @@ impl ObjectStore { self.heap.end_read_tracking_details(token) } + /// VM/宿主判断当前是否位于任意只读作用域,不表示所有新建对象也不可写。 pub fn is_read_only(&self) -> bool { self.heap.is_read_only() } + /// 宿主缓存或类型配置把有效强句柄转为弱句柄;不增加计数,也不消耗调用方已有根。 pub fn downgrade(&self, reference: ObjectRef) -> Result { self.object(reference)?; Ok(WeakObjectRef(reference)) } - /// Returns a new owned root, or None when the original object was released. - /// A foreign store is an error, not an expired reference. + /// 宿主升级弱句柄:存活则 retain 一份外部根,已释放返回 None;跨堆引用仍是错误。 pub fn upgrade(&mut self, weak: WeakObjectRef) -> Result, VmError> { + // 先验证代际与堆身份;只有 InvalidHandle 被解释为过期,其他错误原样传播。 match self.object(weak.0) { Ok(_) => { self.retain(weak.0)?; @@ -107,20 +122,23 @@ impl ObjectStore { } } + /// Vm::take_changes 及宿主缓存取走累计变更批次;清空待取记录,不重置对象版本。 pub fn take_changes(&mut self) -> ChangeBatch { self.heap.take_changes() } + /// 缓存读取对象版本;通过 object 进入,若正在收集依赖也会记录此对象的读取。 pub fn change_version(&self, reference: ObjectRef) -> Result { Ok(self.object(reference)?.header().change_version()) } - /// Replace a host payload transactionally, including all owned GC edges. + /// 宿主提交新载荷及其全部引用边;保持对象身份、属性和头部类型,校验失败不替换载荷。 pub fn replace_payload( &mut self, reference: ObjectRef, payload: impl ObjectPayload, ) -> Result<(), VmError> { + // Type 定义不可被换成其他载荷,防止类型布局在创建后发生变化。 if self .object(reference)? .payload() @@ -131,6 +149,7 @@ impl ObjectStore { "replace immutable type definition", )); } + // Trait 定义同样不可替换,防止已登记的能力契约发生隐式变化。 if self .object(reference)? .payload() @@ -141,44 +160,56 @@ impl ObjectStore { "replace immutable trait definition", )); } + // 先核对内置载荷结构,再交由 Heap 验证 trace 强边与引用计数。 self.validate_builtin(&payload)?; self.heap.replace_payload(reference, payload) } + /// eval::assignment_value 在新旧值都是对象时调用;true 表示更新旧对象并保留其身份。 + /// false 表示未执行此类原地赋值,执行器可改用传入的新对象引用。 pub fn assign_from(&mut self, target: ObjectRef, source: ObjectRef) -> Result { + // 两端必须是本堆存活对象;不能把异堆或过期句柄传给宿主赋值协议。 self.object(target)?; self.object(source)?; + // 优先采用托管 Cell 协议;载荷负责校验来源和领域环路,Store 负责实际更新引用边。 if let Some(cell) = self .object(target)? .payload() .assignment_cell(source, self)? { + // 转发对象本身也受只读保护,不能只检查承载引用的 Cell。 self.heap.prepare_change(target)?; - // Replacing the Cell edge may otherwise reclaim a target which is - // reachable only through the edge being removed. + // 更新 Cell 可能移除维持 target 存活的最后一条内部边;临时根保证记录变更前仍存活。 self.retain(target)?; + // 无论写入成功与否都释放临时根;仅成功时通知 target 的观察者。 let result = self.cell_set(cell, Value::Object(source)); + // Cell 自己会记录变更;这里另记转发对象的 Contents,覆盖经转发器读取的依赖。 if result.is_ok() { self.heap.record_change(target, ChangeKey::Contents); } self.release(target)?; return result.map(|()| true); } + // 仅引用赋值载荷承诺 legacy assign_from 总是 false,可在只读局部重绑定时跳过写屏障。 if self.is_read_only() && self.object(target)?.payload().reference_assignment_only() { return Ok(false); } + // 旧式宿主原地赋值在调用前检查写屏障;实现不得悄悄改变 trace 中的托管边。 self.heap.prepare_change(target)?; let changed = self .object(target)? .payload() .assign_from(self.object(source)?.payload())?; + // false 不产生变更;true 表示宿主已更新载荷,发布一次 Contents 通知。 if changed { self.heap.record_change(target, ChangeKey::Contents); } Ok(changed) } + /// Vm::new 及独立宿主创建空堆;不隐式创建语言根类型或内置函数。 pub fn new() -> Result { + // 初始没有函数类型配置,检查开关关闭,递归校验集合按需要惰性填充。 Ok(Self { heap: Heap::new()?, function_type: None, @@ -188,25 +219,34 @@ impl ObjectStore { }) } + /// 宿主/测试查询存活堆对象数;不是累计分配数,也不含直接 Value 标量。 pub fn object_count(&self) -> usize { self.heap.len() } - /// Returns one external strong reference. Copies of the returned handle - /// are borrowed; use retain/release to manage additional owned references. + /// new_*、类型构造及宿主创建载荷的共同入口;成功返回一份外部强根。 + /// 复制返回句柄不会 retain,额外所有权须显式 retain/release。 pub fn allocate(&mut self, payload: impl ObjectPayload) -> Result { + // 先核对内置载荷结构,再交由 Heap 验证 trace 强边与引用计数。 self.validate_builtin(&payload)?; + // 函数分类由载荷声明;不是所有可调用对象(例如 Type)都自动附着 Function 类型。 let function = payload.is_function(); + // Heap 分配稳定的对象盒并登记载荷强边;从此该对象已持有一份外部根。 let object = self.heap.allocate(Box::new(payload))?; + // 只有函数且配置过 Function 类型才附着;过期配置是错误,不静默创建无类型函数。 if function && let Some(weak) = self.function_type { + // 局部闭包收集附着结果,以便外层统一清理新对象的根。 let result = (|| { + // 升级得到临时类型根,附着后对象自身的类型边承担持有责任。 let definition = self.upgrade(weak)?.ok_or(VmError::InvalidArgument( "configured Function type has expired", ))?; + // 保存附着结果,先释放升级产生的根,再把附着错误交给外层处理。 let result = self.attach_type(object, definition); self.release(definition)?; result })(); + // 类型附着失败时释放本次分配的对象,不向调用者泄漏半初始化结果。 if let Err(error) = result { self.release(object)?; return Err(error); @@ -215,14 +255,18 @@ impl ObjectStore { Ok(object) } + /// Vm::set_function_type 在根类型初始化时配置后续函数的名义类型;同一类型可重复设置。 pub fn set_function_type(&mut self, definition: ObjectRef) -> Result<(), VmError> { self.payload::(definition, "Type")?; + // 配置属于持久状态,不能由只读调用改变;即使传入相同配置也拒绝。 if self.is_read_only() { return Err(VmError::UnsupportedOperation( "configure Function type during read-only call", )); } + // 只保存弱引用;类型生命周期由根类型集合或已附着的对象负责。 let weak = self.downgrade(definition)?; + // 已配置后不能换成另一个类型,避免新旧函数的隐式类型规则不一致。 if self.function_type.is_some_and(|old| old != weak) { return Err(VmError::InvalidArgument( "Function type is already configured", @@ -232,28 +276,35 @@ impl ObjectStore { Ok(()) } + /// Vm::new_object/宿主创建普通可附属性的 Record,名义类型由调用者另行附着。 pub fn new_object(&mut self) -> Result { self.allocate(Record) } + /// 宿主及各访问器借用完整对象;验证句柄并在只读收集上下文登记当前版本读取。 pub fn object(&self, reference: ObjectRef) -> Result<&EndorObject, VmError> { + // 不增加外部根;返回引用受 self 的 Rust 借用期约束。 let object = self.heap.get(reference)?; self.heap .record_read(reference, object.header().change_version()); Ok(object) } - /// Borrowed storage path: depend on Contents for intermediate objects and - /// Attribute(name) on the final storage object. Every handle is validated. + /// 属性读写与类型协议沿 attribute_target 查找实际存储对象,返回含起点和终点的借用句柄链。 + /// 此处每一跳通过 object 记录整对象读取;最终具体属性的读取由调用方继续处理。 pub fn attribute_path(&self, reference: ObjectRef) -> Result, VmError> { + // path 保存访问顺序,seen 检测转发环,current 是本轮需要验证并展开的对象。 let mut path = Vec::new(); let mut seen = std::collections::BTreeSet::new(); let mut current = reference; + // 每次至少加入一个对象;遇到无转发目标才正常结束,因此成功路径一定非空。 loop { + // 重访同一身份说明转发环路,不能靠无限追踪寻找属性终点。 if !seen.insert(current) { return Err(VmError::InvalidArgument("cyclic attribute storage")); } path.push(current); + // 有目标则继续;目标在下一轮验证归属/存活;None 表示该对象自行存储属性。 match self.object(current)?.payload().attribute_target(self)? { Some(target) => current = target, None => return Ok(path), @@ -261,54 +312,68 @@ impl ObjectStore { } } + /// VM 执行槽和宿主把借用句柄变为额外拥有的根,同时增加 external 和 strong 计数。 pub fn retain(&mut self, reference: ObjectRef) -> Result<(), VmError> { self.heap.retain(reference) } + /// VM 清理槽或宿主归还一份外部根;计数降零时连带释放对象拥有的内部边。 + /// 没有外部根时会报错,不能用它归还仅由其他对象持有的内部边。 pub fn release(&mut self, reference: ObjectRef) -> Result<(), VmError> { self.heap.release(reference) } + /// 测试/诊断查询总强计数,包含外部根和堆内强边,不新增读取版本依赖。 pub fn ref_count(&self, reference: ObjectRef) -> Result { Ok(self.heap.get(reference)?.header().ref_count()) } + /// VM/宿主做身份比较:两端均先验证,随后比较完整堆身份、槽与代际,不调用载荷相等协议。 pub fn is_same(&self, left: ObjectRef, right: ObjectRef) -> Result { self.heap.get(left)?; self.heap.get(right)?; Ok(left == right) } + /// Vm::repr 委托载荷生成诊断表示;直接 heap.get,不自动记录整对象读取。 pub fn repr(&self, reference: ObjectRef) -> Result { self.heap.get(reference)?.payload().repr(reference) } + /// Vm::str 委托用户可见文本表示,默认载荷实现回退到 repr。 pub fn str(&self, reference: ObjectRef) -> Result { self.heap.get(reference)?.payload().str(reference) } + /// VM 真值转换调用载荷协议;错误保持为错误,不把协议失败解释为 false。 pub fn is_true(&self, reference: ObjectRef) -> Result { self.heap.get(reference)?.payload().is_true() } + /// 通用字典键准入及宿主哈希调用载荷协议,载荷可明确拒绝哈希。 pub fn hash(&self, reference: ObjectRef) -> Result { self.heap.get(reference)?.payload().hash(reference) } + /// VM 对象比较和字典对象键查找共用的双向协议;不运行源码函数比较方法。 pub fn compare( &self, left: ObjectRef, right: ObjectRef, operation: CompareOp, ) -> Result { + // 借用双方载荷;这条底层协议路径不通过 object,因此不自动收集对象版本依赖。 let left_payload = self.heap.get(left)?.payload(); let right_payload = self.heap.get(right)?.payload(); + // 左侧能处理就立即返回;Some(false) 也是已实现结果,不能继续尝试右侧。 if let Some(result) = left_payload.compare(right_payload, operation)? { return Ok(result); } + // 右侧收到对调后的运算(如 a < b 变为 b > a),仅在左侧返回 None 时调用。 if let Some(result) = right_payload.compare(left_payload, operation.reflected())? { return Ok(result); } + // 两端均未实现:相等/不等回退身份,有序比较不能凭身份定义大小。 match operation { CompareOp::Eq => Ok(left == right), CompareOp::Ne => Ok(left != right), @@ -316,34 +381,39 @@ impl ObjectStore { } } - /// Reads a borrowed value. An Object result needs retain() if the host - /// wants it to survive removal of the attribute that currently owns it. + /// Vm::get_attr/宿主读取属性或继承方法;返回对象句柄是借用,跨原持有者寿命需 retain。 pub fn get_attr(&self, reference: ObjectRef, name: &str) -> Result { self.lookup_attr(reference, name) + // 此兼容入口将方法查找结果摊平成函数句柄;接收者绑定由 VM 的方法调用路径处理。 .map(|attribute| match attribute { crate::types::AttributeLookup::Value(value) => value, crate::types::AttributeLookup::Method { function, .. } => Value::Object(function), }) } - /// Object-valued attributes own internal strong references independently - /// of the caller's external references. Failure leaves the field unchanged. + /// Vm::set_attr 和宿主属性写入入口;对象值成为独立内部强边,不消耗传入句柄的外部根。 + /// 写入前核对类型/契约限制及实际存储路径,再由 Heap 执行写屏障与引用更新。 pub fn set_attr( &mut self, reference: ObjectRef, name: impl Into, value: Value, ) -> Result<(), VmError> { + // 名称只拥有一次,后续校验借用,最终交给 Heap 作为属性键。 let name = name.into(); + // 已附着的名义类型不能通过普通属性写入/删除来替换。 if name == "type" && self.type_of(reference)?.is_some() { return Err(VmError::UnsupportedOperation("write object type")); } + // 只有开启声明检查才校验字段值;能力方法的遮蔽限制在下一段独立执行。 if self.field_type_checks { self.check_field_write(reference, &name, &value)?; } + // 转发链每个对象都可能声明能力方法,不能借转发终点的属性绕过方法契约。 for object in self.attribute_path(reference)? { self.check_typed_method_write(object, &name)?; } + // 取经过验证的最后一个对象作为实际属性存储;attribute_path 成功时必非空。 let storage = *self .attribute_path(reference)? .last() @@ -351,13 +421,17 @@ impl ObjectStore { self.heap.set_attr(storage, name, value) } + /// Vm::del_attr/宿主删除属性;先防止删除受保护类型与声明字段,再定位实际存储。 pub fn del_attr(&mut self, reference: ObjectRef, name: &str) -> Result<(), VmError> { + // 开启声明检查后,字段必须维持存在;不能用删除规避字段类型约束。 if self.field_type_checks && self.declared_field(reference, name)?.is_some() { return Err(VmError::UnsupportedOperation("delete declared class field")); } + // 已附着的名义类型不能通过普通属性写入/删除来替换。 if name == "type" && self.type_of(reference)?.is_some() { return Err(VmError::UnsupportedOperation("delete object type")); } + // 取经过验证的最后一个对象作为实际属性存储;attribute_path 成功时必非空。 let storage = *self .attribute_path(reference)? .last() @@ -365,16 +439,17 @@ impl ObjectStore { self.heap.del_attr(storage, name) } - /// Validate a borrowed object handle without taking an owned reference. + /// 容器写入等路径验证 Value:仅对象变体需要检查句柄,不 retain,也不登记内容读取。 pub fn validate_value(&self, value: &Value) -> Result<(), VmError> { + // 直接值没有堆归属;对象值必须属于本堆且对应存活代际。 if let Value::Object(reference) = value { self.heap.get(*reference)?; } Ok(()) } - /// Explicit full collection. Execution slots use owned roots, so values - /// on live execution stacks participate in the same root accounting. + /// Vm::collect_cycles/宿主显式回收没有外部根可达的环;返回回收对象数。 + /// 活跃执行槽以外部根计数,正常参与存活判定,不另建特殊的 VM 栈扫描通道。 pub fn collect_cycles(&mut self) -> usize { self.heap.collect_cycles() } -- 2.52.0 From 9861a6d58696e2dfcf312d4738f74b134b28d258 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:18:34 +0800 Subject: [PATCH 04/62] docs(objects): explain heap tracking and reference lifecycle --- crates/objects/src/heap.rs | 231 ++++++++++++++++++++++++++++++++++--- 1 file changed, 213 insertions(+), 18 deletions(-) diff --git a/crates/objects/src/heap.rs b/crates/objects/src/heap.rs index cafdb27..c10d265 100644 --- a/crates/objects/src/heap.rs +++ b/crates/objects/src/heap.rs @@ -1,3 +1,7 @@ +//! ObjectStore 的底层对象堆;不执行语言指令,负责稳定对象盒、代际句柄、引用边和回收。 +//! Store/types/store_builtins 先做领域校验,再调用这里的受控修改入口。 +//! 读追踪用 Mutex 支持 &self 协议路径记录观察;对象修改仍要求 &mut self,并非并发可变堆。 + use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::fmt; use std::sync::Mutex; @@ -8,81 +12,111 @@ use crate::changes::MAX_PENDING_CHANGES; use crate::{ChangeBatch, ChangeKey, ObjectChange}; use crate::{EndorObject, ObjectHeader, ObjectPayload, Value, VmError}; +// 进程级单调身份源;Relaxed 只需要原子唯一性,不承担对象发布或内存同步。 static NEXT_HEAP_ID: AtomicU64 = AtomicU64::new(1); -/// Checked, process-local reference. Copying it never retains an object. -/// The generation prevents a released handle from aliasing a reused slot. +/// 进程内受校验句柄;复制不会 retain。heap_id 防跨堆,generation 防止旧句柄别名复用槽。 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ObjectRef { + // 分配它的堆身份。 heap_id: u64, + // slots 向量中的位置,不是对象内存地址。 index: usize, + // 槽释放后递增的代际。 generation: u64, } impl fmt::Display for ObjectRef { + /// ObjectRef 的日志/错误格式化入口;输出进程内身份三元组,不代表可持久化或跨进程引用。 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}:{}:{}", self.heap_id, self.index, self.generation) } } -/// A non-owning reference. Only the originating store can upgrade it; slot -/// reuse never makes a dead weak reference point at a different object. +/// 非拥有弱句柄;仅原 Store 可升级,代际检查保证槽复用不会复活过期弱引用。 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WeakObjectRef(pub(crate) ObjectRef); #[derive(Debug)] struct Slot { + // 当前槽代际;空槽仍保留历史代际,耗尽后不进入 free。 generation: u64, + // Box 保持对象地址稳定,外层 slots 扩容不会移动对象实体。 object: Option>, } #[derive(Debug)] +/// 一层只读作用域;protected 是进入时的存活身份快照,其余字段只收集借用观察。 struct ReadOnly { + // 该层不可修改的已有对象,后续新对象不在此集合中。 protected: BTreeSet, + // 兼容的对象级去重读取集合。 reads: Mutex>, + // 子区间栈;bool 表示是否发生程序内容读取(而非仅验证)。 regions: Mutex>>, + // 按依赖键去重,保留该层首次观察的版本/名称值。 dependencies: Mutex>, + // 与 regions 同步压栈/弹栈的精确子区间依赖。 dependency_regions: Mutex>>, } #[derive(Debug)] +/// ObjectStore 独占的对象槽及生命周期状态;其他模块不能直接修改槽或引用计数。 pub(crate) struct Heap { + // 本堆的进程内唯一身份。 id: u64, + // 已分配过的槽,可含空槽;对象实体单独装箱。 slots: Vec, + // 可复用空槽索引,按栈顺序取回。 free: Vec, + // 实际占用槽数,分配/摘除时同步维护。 live: usize, + // 同一对象/变更键只保留最新版本,限制通知内存占用。 changes: BTreeMap<(ObjectRef, ChangeKey), u64>, + // 本批已溢出:停止精确通知,要求消费者全面重验依赖。 changes_overflow: bool, + // 活跃只读作用域栈,任意一层保护对象就禁止修改。 read_only: Vec, + // 当前线程的验证读取嵌套深度,RAII 退出时递减。 validation_reads: Mutex>, } -/// Borrowed read identity; content_read distinguishes ordinary reads from -/// annotation validation alone. Neither form retains the object. +/// 借用读取身份及用途分类,不创建 GC 根;普通内容读取会覆盖“仅验证”的分类。 #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ReadAccess { + /// 读取的对象身份。 pub object: ObjectRef, + /// true 表示至少一次程序内容读取,false 表示只有注解验证读取。 pub content_read: bool, } -/// Borrowed observations; Object handles never retain the observed graph. -/// Named values are immutable comparison snapshots, not managed owning edges. +/// 缓存重验的借用观察;包括 Name.value 内句柄都不持有对象图,不是托管拥有边。 #[derive(Debug, Clone, PartialEq)] pub enum ReadDependency { + /// 整对象版本变化即失效。 Object { + /// 被观察对象的完整身份。 object: ObjectRef, + /// 读取时的版本快照。 version: u64, }, + /// 只要求原对象仍是字典,不依赖其他条目的变化。 DictionaryKind { + /// 已确认具有字典载荷的对象身份。 object: ObjectRef, }, + /// 指定文本名称的命中/缺失值快照;None 与 Some(Nil) 区分。 Name { + /// 接受名称查找的字典身份。 object: ObjectRef, + /// 本次观察的文本键,不代表全局与内置空间的合并查找。 name: String, + /// 首次查到的值或缺失;含对象句柄时也只是比较快照。 value: Option, }, } impl ReadDependency { + /// 缓存/读取分析使用者取得观察所属对象,不创建外部拥有根。 pub fn object(&self) -> ObjectRef { match self { Self::Object { object, .. } @@ -92,6 +126,7 @@ impl ReadDependency { } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +/// 精确依赖的排序/去重键;与 ReadDependency 分开,观察值不参与键身份。 enum ReadKey { Object(ObjectRef), DictionaryKind(ObjectRef), @@ -99,30 +134,41 @@ enum ReadKey { } #[derive(Debug)] pub struct TrackedReads { + /// 子区间对象读取与用途分类。 pub accesses: Vec, + /// 精确观察列表,供 Store::read_dependency_valid 重验。 pub dependencies: Vec, } #[derive(Debug)] pub struct ReadSet { + /// 整个作用域读取到的借用对象身份。 pub objects: Vec, + /// 精确观察列表,供 Store::read_dependency_valid 重验。 pub dependencies: Vec, } +/// 仅借用堆的验证读取守卫;不拥有对象,也不延长只读作用域寿命。 struct ValidationScope<'a> { + // 被借用的堆,守卫存活期间保证分类表可访问。 heap: &'a Heap, + // 进入验证的线程,用于归还同一线程的嵌套深度。 thread: ThreadId, } impl Drop for ValidationScope<'_> { + /// 验证读取 RAII 清理:compute 正常返回、提前报错或展开栈时都归还当前线程的一层标记。 fn drop(&mut self) { + // 在锁下找到本线程深度;守卫只在成功增加深度后创建。 let mut active = self .heap .validation_reads .lock() .expect("read tracker poisoned"); + // 嵌套验证只减少本层,不清除仍活跃的外层验证标记。 let depth = active .get_mut(&self.thread) .expect("active validation scope"); *depth -= 1; + // 最外层退出时移除线程项,后续读取重新归类为程序内容读取。 if *depth == 0 { active.remove(&self.thread); } @@ -130,23 +176,32 @@ impl Drop for ValidationScope<'_> { } impl Heap { + /// ObjectStore::new 创建空堆;分配独立进程内身份,计数器耗尽时报错而不循环复用。 pub(crate) fn new() -> Result { + // checked_add 防止身份绕回;fetch_update 返回更新前的唯一值。 let id = NEXT_HEAP_ID .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1)) .map_err(|_| VmError::IdentityExhausted)?; + // 初始没有对象、空槽、通知或读取作用域。 Ok(Self { id, + // 已分配过的槽,可含空槽;对象实体单独装箱。 slots: Vec::new(), + // 可复用空槽索引,按栈顺序取回。 free: Vec::new(), live: 0, + // 同一对象/变更键只保留最新版本,限制通知内存占用。 changes: BTreeMap::new(), changes_overflow: false, read_only: vec![], + // 当前线程的验证读取嵌套深度,RAII 退出时递减。 validation_reads: Mutex::new(HashMap::new()), }) } + /// types 的字段/方法契约变更校验遍历存活对象时使用;输出借用身份,不增加根计数。 pub(crate) fn live_references(&self) -> impl Iterator + '_ { + // 空槽跳过;按每个当前代际重建身份,而不是复用历史外部句柄。 self.slots.iter().enumerate().filter_map(|(index, slot)| { slot.object.as_ref().map(|_| ObjectRef { heap_id: self.id, @@ -156,7 +211,9 @@ impl Heap { }) } + /// Store/VM 进入只读调用时快照现有身份;返回当前嵌套深度作为结束令牌。 pub(crate) fn begin_read_only(&mut self) -> usize { + // 只快照已存在对象;外层新建对象在随后嵌套层进入时也会变为该层受保护对象。 let protected = self .slots .iter() @@ -169,23 +226,32 @@ impl Heap { }) }) .collect(); + // 新层观察容器独立,之后的读取还会传播到适用的外层。 self.read_only.push(ReadOnly { protected, + // 兼容的对象级去重读取集合。 reads: Mutex::new(BTreeSet::new()), + // 子区间栈;bool 表示是否发生程序内容读取(而非仅验证)。 regions: Mutex::new(Vec::new()), + // 按依赖键去重,保留该层首次观察的版本/名称值。 dependencies: Mutex::new(BTreeMap::new()), + // 与 regions 同步压栈/弹栈的精确子区间依赖。 dependency_regions: Mutex::new(Vec::new()), }); self.read_only.len() } + /// Store 的兼容结束入口,仅返回读取对象身份,不保留精确观察键。 pub(crate) fn end_read_only(&mut self, scope: usize) -> Result, VmError> { Ok(self.end_read_only_precise(scope)?.objects) } + /// Store/VM 结束最内层只读作用域并取走全部观察;错误令牌不弹出状态。 pub(crate) fn end_read_only_precise(&mut self, scope: usize) -> Result { + // 令牌是深度而非永久唯一 ID;调用方必须遵守同一作用域内严格 LIFO。 if scope == 0 || scope != self.read_only.len() { return Err(VmError::InvalidArgument("read-only scope nesting mismatch")); } + // 检查已保证栈顶存在,取走 Mutex 内的数据,输出按有序集合/键排列。 let scope = self.read_only.pop().unwrap(); Ok(ReadSet { objects: scope @@ -203,11 +269,15 @@ impl Heap { }) } + /// Store/VM 在当前只读调用内开始一个子调用区间,同时建立分类与精确依赖两套容器。 pub(crate) fn begin_read_tracking(&self) -> Result { + // 子区间必须依附当前最内层只读作用域,不能独立开启读取追踪。 let scope = self.read_only.last().ok_or(VmError::InvalidArgument( "read tracking requires a read-only scope", ))?; + // 锁的取得顺序与结束路径一致:先分类区间,再精确依赖区间。 let mut regions = scope.regions.lock().expect("read tracker poisoned"); + // 两套栈使用相同深度令牌,不能只创建其中一套。 regions.push(BTreeMap::new()); scope .dependency_regions @@ -217,6 +287,7 @@ impl Heap { Ok(regions.len()) } + /// 兼容子区间结束入口,只保留读取对象列表,丢弃分类与精确依赖。 pub(crate) fn end_read_tracking(&self, token: usize) -> Result, VmError> { Ok(self .end_read_tracking_details(token)? @@ -225,24 +296,30 @@ impl Heap { .collect()) } + /// Store/VM 结束子区间,保留程序内容读取与注解验证读取的分类。 pub(crate) fn end_read_tracking_details( &self, token: usize, ) -> Result, VmError> { Ok(self.end_read_tracking_precise(token)?.accesses) } + /// 精确结束子调用区间;同时弹出两套追踪栈,外层区间和只读作用域继续有效。 pub(crate) fn end_read_tracking_precise(&self, token: usize) -> Result { + // 子区间必须依附当前最内层只读作用域,不能独立开启读取追踪。 let scope = self.read_only.last().ok_or(VmError::InvalidArgument( "read tracking requires a read-only scope", ))?; + // 锁的取得顺序与结束路径一致:先分类区间,再精确依赖区间。 let mut regions = scope.regions.lock().expect("read tracker poisoned"); let mut precise = scope .dependency_regions .lock() .expect("read tracker poisoned"); + // 同时检查两套栈,错误不弹出任何一边,避免分类与精确观察错位。 if token == 0 || token != regions.len() || token != precise.len() { return Err(VmError::InvalidArgument("read tracking nesting mismatch")); } + // 将最内层区间移出;外层已在记录时同步收到观察,不需要结束时再合并。 Ok(TrackedReads { accesses: regions .pop() @@ -257,30 +334,39 @@ impl Heap { }) } + /// Store 的写配置保护和 VM 效果判断查询是否存在只读作用域。 pub(crate) fn is_read_only(&self) -> bool { !self.read_only.is_empty() } + /// types::matches_annotation 在类型校验期间调用,将该线程内的读取标为验证用途。 pub(crate) fn with_validation_reads( &self, compute: impl FnOnce() -> Result, ) -> Result { + // 无活跃只读作用域就没有需要收集/分类的观察。 if self.read_only.is_empty() { return compute(); } + // 分类标记按线程隔离,嵌套深度使内层退出不影响外层。 let thread = std::thread::current().id(); { + // 限定锁作用域,不能持锁执行用户传入的 compute。 let mut active = self.validation_reads.lock().expect("read tracker poisoned"); + // 溢出时不写回新深度,也不建立清理守卫。 let depth = active.entry(thread).or_default(); *depth = depth .checked_add(1) .ok_or(VmError::ExecutionLimit("validation scopes"))?; } + // 守卫覆盖整个 compute;? 提前返回或 panic 展开也会恢复分类标记。 let _scope = ValidationScope { heap: self, thread }; compute() } + /// Store::object 取得对象后登记整对象版本观察;本方法不验证句柄也不 retain。 pub(crate) fn record_read(&self, reference: ObjectRef, version: u64) { + // 无活跃只读作用域就没有需要收集/分类的观察。 if self.read_only.is_empty() { return; } @@ -291,17 +377,20 @@ impl Heap { } }); } + /// store_builtins::dictionary_kind 已验证后登记“仍是字典”,不依赖字典全部条目。 pub(crate) fn record_dictionary_kind(&self, reference: ObjectRef) { self.record_observation(reference, ReadKey::DictionaryKind(reference), || { ReadDependency::DictionaryKind { object: reference } }); } + /// store_builtins::dict_get_name 登记命中值或缺失快照,供之后按名称重验依赖。 pub(crate) fn record_name( &self, reference: ObjectRef, name: &str, value: &Option, ) { + // 无活跃只读作用域就没有需要收集/分类的观察。 if self.read_only.is_empty() { return; } @@ -313,28 +402,35 @@ impl Heap { } }); } + /// 三种 record_* 共用的收集器,把观察传播给每个保护该对象的外层作用域及其活跃子区间。 fn record_observation( &self, reference: ObjectRef, key: ReadKey, build: impl Fn() -> ReadDependency, ) { + // 无活跃只读作用域就没有需要收集/分类的观察。 if self.read_only.is_empty() { return; } + // 当前线程在任何验证层内都先分类为验证读取;区间中若另有普通读取仍以普通为准。 let content = !self .validation_reads .lock() .expect("read tracker poisoned") .contains_key(&std::thread::current().id()); + // 所有适用外层都要收到读取,不能只记录最内层导致父调用漏依赖。 for scope in &self.read_only { + // 本层新建结果不属于本层外部输入,不作为该层已有对象依赖。 if scope.protected.contains(&reference) { + // 同键保留首次观察;build 惰性生成快照,未命中时才克隆名称值。 scope .dependencies .lock() .expect("read tracker poisoned") .entry(key.clone()) .or_insert_with(&build); + // 每个活跃子区间独立保存该精确观察。 for region in scope .dependency_regions .lock() @@ -343,11 +439,13 @@ impl Heap { { region.entry(key.clone()).or_insert_with(&build); } + // 兼容对象集合只去重身份,不区分读取用途。 scope .reads .lock() .expect("read tracker poisoned") .insert(reference); + // 用 OR 合并分类,避免后来发生的验证读取覆盖已有程序内容读取。 for region in scope .regions .lock() @@ -363,25 +461,32 @@ impl Heap { } } + /// Store::allocate 提交已校验的载荷;trace 引用边先检查,成功返回一份外部拥有根。 pub(crate) fn allocate( &mut self, payload: Box, ) -> Result { + // 收集每条载荷强边,包括重复项;仅构造身份快照,不立即改计数。 let mut edges = Vec::new(); payload.trace(&mut |reference| edges.push(reference)); + // 先验证所有目标及累积计数溢出,失败时尚未改变堆。 let increments = self.prepare_increments(&edges)?; + // 优先复用已释放且代际未耗尽的空槽,否则追加一个新槽。 let index = self.free.pop().unwrap_or(self.slots.len()); + // 新槽从代际零开始;复用槽保留 take 已递增的代际。 if index == self.slots.len() { self.slots.push(Slot { generation: 0, object: None, }); } + // 完整身份把堆、位置和当前代际绑定,不能用 index 单独代替。 let reference = ObjectRef { heap_id: self.id, index, generation: self.slots[index].generation, }; + // 新对象初始一份外部根、没有名义类型和属性;对象实体在 Box 中地址稳定。 let object = Box::new(EndorObject { header: ObjectHeader { identity: reference, @@ -395,11 +500,13 @@ impl Heap { payload_edges: edges, }); self.apply_increments(increments); + // 引用增量已提交后才发布占用槽,live 与占用状态一起增加。 self.slots[index].object = Some(object); self.live += 1; Ok(reference) } + /// 所有堆操作的只读验证入口;检查堆归属、槽范围、代际及占用状态,不登记读取依赖。 pub(crate) fn get(&self, reference: ObjectRef) -> Result<&EndorObject, VmError> { self.validate_owner(reference)?; self.slots @@ -409,22 +516,27 @@ impl Heap { .ok_or(VmError::InvalidHandle) } + /// types::attach_type 完成类型合法性校验后调用;建立不可替换的头部类型强边。 pub(crate) fn attach_type( &mut self, reference: ObjectRef, type_object: ObjectRef, ) -> Result<(), VmError> { + // 同一类型重复附着是无操作,不产生版本变化或写屏障检查。 if self.get(reference)?.header.type_object == Some(type_object) { return Ok(()); } + // 一旦附着不能改成另一种类型。 if self.get(reference)?.header.type_object.is_some() { return Err(VmError::InvalidArgument("object already has a type")); } + // 防止动态 type 属性与托管头部类型产生两个冲突来源。 if self.get(reference)?.attributes.contains_key("type") { return Err(VmError::InvalidArgument( "type attribute conflicts with managed type", )); } + // 类型边和目标写权限全部预检后,才共同提交计数、头部和版本通知。 let increments = self.prepare_increments(&[type_object])?; self.prepare_change(reference)?; self.apply_increments(increments); @@ -433,21 +545,24 @@ impl Heap { Ok(()) } - /// Transactional copy-on-update for built-in containers. The EndorObject - /// box and identity stay stable; only its payload and edge snapshot change. - /// All new edges are validated/retained before any old edge is released. + /// Store/内置容器的复制后替换入口;保持对象盒与身份,只更新载荷和边快照。 + /// 所有新边先验证并保留,再释放旧边;同一目标同时出现在新旧边中也不会中途被回收。 pub(crate) fn replace_payload( &mut self, reference: ObjectRef, payload: impl ObjectPayload, ) -> Result<(), VmError> { self.get(reference)?; + // 收集每条载荷强边,包括重复项;仅构造身份快照,不立即改计数。 let mut edges = Vec::new(); payload.trace(&mut |target| edges.push(target)); + // 先验证所有目标及累积计数溢出,失败时尚未改变堆。 let increments = self.prepare_increments(&edges)?; self.prepare_change(reference)?; self.apply_increments(increments); + // 写屏障和引用增量已提交,此处只更换载荷及其快照,不移动外层对象。 let object = self.get_mut(reference)?; + // 暂存旧边/旧载荷,等新状态与通知建立后再释放旧状态。 let old_edges = std::mem::replace(&mut object.payload_edges, edges); let old_payload = std::mem::replace(&mut object.payload, Box::new(payload)); self.record_change(reference, ChangeKey::Contents); @@ -456,28 +571,36 @@ impl Heap { Ok(()) } + /// 内部受控修改借用;仍先复用 get 的完整校验,不向 Store 外公开任意可变对象。 fn get_mut(&mut self, reference: ObjectRef) -> Result<&mut EndorObject, VmError> { self.get(reference)?; Ok(self.slots[reference.index].object.as_deref_mut().unwrap()) } + /// get 首先区分跨堆句柄,避免把异堆对象误认为本堆的同号槽。 fn validate_owner(&self, reference: ObjectRef) -> Result<(), VmError> { + // owner 错误独立于槽是否存在,弱引用升级不能把跨堆误判成过期。 if reference.heap_id != self.id { return Err(VmError::ForeignHandle); } Ok(()) } + /// 分配/替换载荷、类型附着、属性写入共同预检引用边;只计算增量,不实际改计数。 fn prepare_increments( &self, references: &[ObjectRef], ) -> Result, VmError> { + // 按目标聚合重复边,既保留多重拥有关系,又能一次预检总增量。 let mut counts = BTreeMap::::new(); + // 第一遍验证每条边并累计次数;没有任何已提交的计数修改。 for reference in references { self.get(*reference)?; + // 连重复次数本身也要 checked_add,不能依赖后续总计数检查替代。 let count = counts.entry(*reference).or_default(); *count = count.checked_add(1).ok_or(VmError::RefCountOverflow)?; } + // 第二遍确保每个目标的当前强计数能容纳完整增量。 for (reference, count) in &counts { self.get(*reference)? .header @@ -488,18 +611,24 @@ impl Heap { Ok(counts) } + /// 上述写入入口在预检和写屏障均通过后提交内部强边增量;不增加外部根。 fn apply_increments(&mut self, increments: BTreeMap) { + // 仅消费刚刚预检的增量;这段提交路径没有新的可恢复失败分支。 for (reference, count) in increments { self.get_mut(reference).unwrap().header.strong_refs += count; } } + /// Store/VM 将借用句柄变为额外外部根;两个计数全部预检成功后再共同更新。 pub(crate) fn retain(&mut self, reference: ObjectRef) -> Result<(), VmError> { + // 取得已验证的头部;外部根与总强计数必须保持一致。 let header = &mut self.get_mut(reference)?.header; + // 先计算两个新值,第二项溢出时第一项也尚未写回。 let strong = header .strong_refs .checked_add(1) .ok_or(VmError::RefCountOverflow)?; + // 外部根同样独立检查溢出,不把内部强边误计为宿主根。 let external = header .external_refs .checked_add(1) @@ -509,13 +638,17 @@ impl Heap { Ok(()) } + /// Store/VM 归还一份外部根;总强计数归零时启动非递归级联释放。 pub(crate) fn release(&mut self, reference: ObjectRef) -> Result<(), VmError> { + // 取得已验证的头部;外部根与总强计数必须保持一致。 let header = &mut self.get_mut(reference)?.header; + // 只有拥有的外部根可 release;借用或内部边不能从这里归还。 if header.external_refs == 0 { return Err(VmError::NoOwnedReference); } header.external_refs -= 1; header.strong_refs -= 1; + // 仍有内部边时留待后续释放或环回收;只有总强计数为零才立即析构。 let pending = if header.strong_refs == 0 { vec![reference] } else { @@ -525,6 +658,7 @@ impl Heap { Ok(()) } + /// Store 定位实际属性存储后写入;先保留新值强边,再替换并释放旧值边。 pub(crate) fn set_attr( &mut self, reference: ObjectRef, @@ -532,24 +666,29 @@ impl Heap { value: Value, ) -> Result<(), VmError> { self.check_attributes(reference)?; + // 只有对象变体形成内部强边,直接标量不参与托管对象计数。 let edges: Vec<_> = match &value { Value::Object(target) => vec![*target], _ => Vec::new(), }; + // 先验证所有目标及累积计数溢出,失败时尚未改变堆。 let increments = self.prepare_increments(&edges)?; self.prepare_change(reference)?; self.apply_increments(increments); + // 写屏障通过后移出旧值;属性更新本身不消耗调用者的外部根。 let old = self .get_mut(reference)? .attributes .insert(name.clone(), value); self.record_change(reference, ChangeKey::Attribute(name)); + // 新对象引用已先保留,最后释放旧属性拥有的那一条内部边。 if let Some(Value::Object(target)) = old { self.release_edges(vec![target]); } Ok(()) } + /// types 的属性查找读取自身字段表;返回借用 Value 表示,继承/转发由上层处理。 pub(crate) fn get_attr(&self, reference: ObjectRef, name: &str) -> Result { self.check_attributes(reference)?; self.get(reference)? @@ -559,23 +698,28 @@ impl Heap { .ok_or_else(|| VmError::AttributeNotFound(name.into())) } + /// Store 定位存储并通过类型保护后删除属性,成功才发通知并释放旧值强边。 pub(crate) fn del_attr(&mut self, reference: ObjectRef, name: &str) -> Result<(), VmError> { self.check_attributes(reference)?; self.prepare_change(reference)?; + // 写屏障通过后移出旧值;属性更新本身不消耗调用者的外部根。 let old = self .get_mut(reference)? .attributes .remove(name) .ok_or_else(|| VmError::AttributeNotFound(name.into()))?; self.record_change(reference, ChangeKey::Attribute(name.into())); + // 删除成功后释放该属性原有的对象边,标量仅由 Rust Drop 清理。 if let Value::Object(target) = old { self.release_edges(vec![target]); } Ok(()) } + /// 所有受控写入在实际修改前调用:拒绝受保护对象,并预检版本是否还能递增。 pub(crate) fn prepare_change(&self, reference: ObjectRef) -> Result<(), VmError> { self.get(reference)?; + // 任意外层保护都有效;不能借进入/退出更内层作用域绕过只读限制。 if self .read_only .iter() @@ -593,28 +737,36 @@ impl Heap { Ok(()) } + /// 受控写入成功后递增对象版本,再合并通知;调用者必须先通过 prepare_change。 pub(crate) fn record_change(&mut self, reference: ObjectRef, key: ChangeKey) { + // prepare_change 已验证版本可递增;失败说明内部调用顺序破坏了约定。 let object = self.get_mut(reference).expect("validated change target"); object.header.change_version += 1; + // 先保存递增后版本,即使不发通知也保持版本真实反映修改。 let version = object.header.change_version; - // Fresh result construction has no existing observers. Keep versions, - // but do not notify application dependencies about derived allocations. + // 只读期间允许的写入只涉及新结果;保留版本,但不向应用发布派生结果构造通知。 if self.is_read_only() { return; } + // 本批已丢失精确信息,继续积累条目也无法补全,等待消费者全面重验。 if self.changes_overflow { return; } + // 同对象/键合并为最新版本;其他键仍分别保留。 let entry = (reference, key); + // 达上限时仅新增键触发溢出;已有键可继续更新。溢出后清空并设置全面重验标志。 if self.changes.len() >= MAX_PENDING_CHANGES && !self.changes.contains_key(&entry) { self.changes.clear(); self.changes_overflow = true; } else { + // 未溢出或仅更新已有键,保存该次提交的版本。 self.changes.insert(entry, version); } } + /// Store/宿主取走本批通知及溢出标志,使后续写入开始新的批次。 pub(crate) fn take_changes(&mut self) -> ChangeBatch { + // mem::take 将记录和标志一起归还调用者,本堆恢复空批次。 ChangeBatch { changes: std::mem::take(&mut self.changes) .into_iter() @@ -628,18 +780,25 @@ impl Heap { } } + /// 属性读写共用载荷能力检查;不允许字段的载荷即使没有该属性也先报协议错误。 fn check_attributes(&self, reference: ObjectRef) -> Result<(), VmError> { + // get 先验证身份;协议能力由载荷声明,不由属性表是否为空推断。 if !self.get(reference)?.payload.allows_attributes() { return Err(VmError::UnsupportedOperation("attributes")); } Ok(()) } + /// 载荷替换或属性更新移除旧内部边;逐边减强计数,随后统一处理新出现的零计数对象。 fn release_edges(&mut self, edges: Vec) { + // 工作栈只保存待处理身份,不拥有额外根;用于批量级联或可达性遍历。 let mut pending = Vec::new(); + // 每条旧边恰好减一次,重复边不能去重。 for target in edges { + // 已登记强边的目标应仍存活;这里 unwrap 表示内部图计数不变量。 let header = &mut self.get_mut(target).unwrap().header; header.strong_refs -= 1; + // 最后一条强边移除后加入工作栈,稍后再展开其拥有边。 if header.strong_refs == 0 { pending.push(target); } @@ -647,11 +806,13 @@ impl Heap { self.drain_zero_counts(pending); } - /// Removes the object before running Drop. Reuse increments its generation; - /// an exhausted generation permanently retires that slot. + /// 级联释放和环回收先从槽中摘除对象,再允许 Rust Drop;只接收已知存活身份。 fn take(&mut self, reference: ObjectRef) -> Box { + // 取走盒后槽立即不可访问,析构尚未发生。 let slot = &mut self.slots[reference.index]; + // 所有调用方都先证明对象存在,因此这里无需恢复性错误分支。 let object = slot.object.take().unwrap(); + // 仅能递增代际的空槽才加入 free;耗尽时永久停用,防身份绕回。 if let Some(next) = slot.generation.checked_add(1) { slot.generation = next; self.free.push(reference.index); @@ -660,12 +821,18 @@ impl Heap { object } + /// release/release_edges 共用工作栈,避免长引用链触发递归析构栈溢出。 fn drain_zero_counts(&mut self, mut pending: Vec) { + // 用显式工作栈逐个处理,避免对深链递归。 while let Some(reference) = pending.pop() { + // 先摘除自己,再递减出边;归零对象不可能仍有一条计入自身的有效强入边。 let object = self.take(reference); + // edges 枚举头部类型、载荷快照和属性边,均参与生命周期维护。 for target in object.edges() { + // 已登记强边的目标应仍存活;这里 unwrap 表示内部图计数不变量。 let header = &mut self.get_mut(target).unwrap().header; header.strong_refs -= 1; + // 最后一条强边移除后加入工作栈,稍后再展开其拥有边。 if header.strong_refs == 0 { pending.push(target); } @@ -674,25 +841,31 @@ impl Heap { } } - /// Full, explicit cycle collection. External references are roots; every - /// managed attribute and declared payload edge participates in tracing. + /// Store 显式全堆环回收:从外部根标记可达对象,余下对象统一摘除并释放。 pub(crate) fn collect_cycles(&mut self) -> usize { + // 标记按槽索引存储;本次 &mut self 独占期间槽不会被其他调用修改。 let mut marked = vec![false; self.slots.len()]; + // 工作栈只保存待处理身份,不拥有额外根;用于批量级联或可达性遍历。 let mut pending = Vec::new(); + // 只有外部根作为起点;纯内部互引环即使 strong_refs 非零也不是根。 for slot in &self.slots { + // 跳过空槽和没有外部根的对象,后者仍可能经其他根遍历到。 if let Some(object) = &slot.object && object.header.external_refs > 0 { pending.push(object.header.identity); } } + // 从根沿强边展开对象图,工作栈允许重复入栈,marked 保证只展开一次。 while let Some(reference) = pending.pop() { + // 已标记身份无需重复展开,保证带环图遍历会终止。 if marked[reference.index] { continue; } marked[reference.index] = true; pending.extend(self.get(reference).unwrap().edges()); } + // 所有未标记的占用槽都是垃圾;空槽不进入集合。 let garbage: Vec<_> = self .slots .iter() @@ -700,23 +873,29 @@ impl Heap { .filter(|(index, _)| !marked[*index]) .filter_map(|(_, slot)| slot.object.as_ref().map(|object| object.header.identity)) .collect(); + // 先摘除整批垃圾,避免环内互引让析构依赖逐对象处理的次序。 let objects: Vec<_> = garbage .iter() .map(|reference| self.take(*reference)) .collect(); - // All garbage slots are detached before touching edges or running Drop. + // 垃圾槽已全部摘除:垃圾到垃圾的边无需再减计数,只有指向幸存者的边要归还。 + // 用已取出的对象盒枚举边快照,不尝试通过失效句柄重新借用垃圾对象。 for object in &objects { + // edges 枚举头部类型、载荷快照和属性边,均参与生命周期维护。 for target in object.edges() { + // 仍可 get 的目标是外部根可达幸存者;它的强计数扣除来自垃圾的一条边。 if let Ok(survivor) = self.get_mut(target) { survivor.header.strong_refs -= 1; } } } + // 返回摘除对象数;最后统一 Drop 释放宿主资源。 let collected = objects.len(); drop(objects); collected } + /// Store::object_count 查询当前存活对象数,不包含已释放空槽。 pub(crate) fn len(&self) -> usize { self.live } @@ -728,13 +907,18 @@ mod tests { use crate::Record; #[test] + /// 内嵌回归测试:伪造越界槽与错误代际,分别验证不可变/可变入口都拒绝。 fn invalid_slots_and_generations_are_rejected() { + // 测试直接使用内部 Heap,以便注入公共 Store API 不允许构造的异常状态。 let mut heap = Heap::new().unwrap(); + // 一个真实有效的对象作为基准,之后只篡改句柄中的单个维度。 let reference = heap.allocate(Box::new(Record)).unwrap(); + // 保持堆身份与代际有效,仅让槽越界,隔离范围检查。 let invalid = ObjectRef { index: usize::MAX, ..reference }; + // 保持槽位置有效,仅伪造代际,覆盖可变借用路径的同一校验。 let stale = ObjectRef { generation: 1, ..reference @@ -744,15 +928,21 @@ mod tests { } #[test] + /// 内嵌回归测试:槽复用会改变代际,代际耗尽则永久退休,不让旧句柄指向新对象。 fn reused_slots_never_alias_and_exhausted_generations_retire() { + // 测试直接使用内部 Heap,以便注入公共 Store API 不允许构造的异常状态。 let mut heap = Heap::new().unwrap(); + // 先释放第一个对象,使它的槽成为下一次分配的复用候选。 let first = heap.allocate(Box::new(Record)).unwrap(); heap.release(first).unwrap(); + // 复用索引相同、代际不同,旧身份必须失效。 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; + // 构造与被注入槽一致的当前有效身份,避免把测试误变成代际不匹配。 let last = ObjectRef { generation: u64::MAX, ..second @@ -764,15 +954,20 @@ mod tests { .header .identity = last; heap.release(last).unwrap(); + // 耗尽槽不得复用,新的对象只能取得其他索引。 let next = heap.allocate(Box::new(Record)).unwrap(); assert_ne!(next.index, last.index); } #[test] + /// 内嵌回归测试:注入强计数上限,确认 retain 与属性写入溢出时没有部分提交。 fn failed_reference_increments_leave_counts_and_attributes_unchanged() { + // 测试直接使用内部 Heap,以便注入公共 Store API 不允许构造的异常状态。 let mut heap = Heap::new().unwrap(); + // owner 接收属性边,target 模拟无法再增加强计数的对象。 let owner = heap.allocate(Box::new(Record)).unwrap(); let target = heap.allocate(Box::new(Record)).unwrap(); + // 仅改强计数,外部根仍为一;后面的断言检查失败是否意外改动 external_refs。 heap.get_mut(target).unwrap().header.strong_refs = usize::MAX; assert_eq!(heap.retain(target), Err(VmError::RefCountOverflow)); assert_eq!(heap.get(target).unwrap().header.external_refs, 1); -- 2.52.0 From 717eab6d3223dade7ad120d6f77f5a945eab42fd Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:19:52 +0800 Subject: [PATCH 05/62] docs(objects): explain change notifications and shared errors --- crates/objects/src/changes.rs | 19 ++++++++++--- crates/objects/src/error.rs | 50 +++++++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/crates/objects/src/changes.rs b/crates/objects/src/changes.rs index a48d5d8..e07aa5a 100644 --- a/crates/objects/src/changes.rs +++ b/crates/objects/src/changes.rs @@ -1,26 +1,37 @@ +//! Heap 产生、Store/VM 取走、宿主缓存消费的变更通知数据;不保存对象所有权。 +//! 通知反映已提交的修改,不是可重放操作日志,也不保证消费时源对象仍存活。 + use crate::ObjectRef; -/// A precise field change or a replacement of an object's native contents. -/// Entries are notifications, never roots; a source can be dead when consumed. +/// Heap::record_change 用于合并通知的键;同一对象的不同属性仍各自保留。 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum ChangeKey { + /// Heap 属性写入/删除发布具体字段名;消费者可只失效对应字段的观察。 Attribute(String), + /// 载荷替换、类型附着或共享引用更新改变整体内容;消费者按内容依赖处理。 Contents, } +/// Heap::take_changes 从内部有序映射生成的单条合并通知,供宿主失效缓存。 #[derive(Debug, Clone, PartialEq, Eq)] pub struct ObjectChange { + /// 修改来源的完整借用身份,包含代际;不能当作有效拥有根直接使用。 pub object: ObjectRef, + /// 被修改的字段或整体载荷类别。 pub key: ChangeKey, + /// 此对象/键在本批最后一次通知的对象版本,不是批次序号。 pub version: u64, } -/// Coalesced changes since the previous drain. Overflow requires the consumer -/// to rescan its dependencies, rather than silently missing notifications. +/// Store::take_changes 返回上次取走后累计的合并结果;取走时重置内部批次。 +/// 溢出会丢弃精确条目并要求全面重验,消费者不能把空 changes 解释为没有修改。 #[derive(Debug, Default)] pub struct ChangeBatch { + /// 精确通知;同一对象/键重复修改仅保留最新版本,排序不表示实际修改先后。 pub changes: Vec, + /// true 时须全面重验所持依赖,不能仅处理 changes 中的对象。 pub rescan_required: bool, } +/// Heap::record_change 的唯一对象/键数量上限;更新已有键不额外占用容量。 pub(crate) const MAX_PENDING_CHANGES: usize = 4096; diff --git a/crates/objects/src/error.rs b/crates/objects/src/error.rs index 93af03e..15bd08c 100644 --- a/crates/objects/src/error.rs +++ b/crates/objects/src/error.rs @@ -1,55 +1,97 @@ +//! objects 定义、compiler/core 与宿主共用的错误协议,避免底层存储反向依赖 VM 执行器。 +//! Display 只生成诊断文本,不恢复对象、不回滚操作,也不等同于托管的异常对象载荷。 + use std::fmt; -/// Shared object/bytecode/execution errors. The established VmError name is -/// retained for embedders; defining it here avoids a dependency on the VM. +/// 覆盖对象、字节码与执行错误;保留 VmError 名称供既有嵌入方匹配。 +/// 各操作用 Result 返回;Clone 复制诊断信息,不复制 VM 状态或托管对象。 #[derive(Debug, Clone, PartialEq, Eq)] pub enum VmError { + /// 宿主等待对象的异步操作失败,携带提供方报告的原因。 AwaitFailed(String), + /// 宿主等待对象报告取消,与失败原因或暂未就绪区分。 AwaitCancelled, + /// Heap::get 发现句柄属于其他堆;不是本堆对象已过期。 ForeignHandle, + /// Heap::get 发现越界、代际不符或空槽;不能再访问该身份。 InvalidHandle, + /// Heap::new 的进程级身份计数耗尽,拒绝复用身份。 IdentityExhausted, + /// Heap 引用增量预检溢出,阻止计数绕回。 RefCountOverflow, + /// Heap::release 没有可归还的外部根;内部强边不能当外部根释放。 NoOwnedReference, + /// 属性存储查找/删除未找到字段,携带字段名。 AttributeNotFound(String), + /// 载荷不支持操作或当前上下文禁止操作,携带静态操作说明。 UnsupportedOperation(&'static str), + /// 构造或协议校验不满足结构约束,携带静态原因。 InvalidArgument(&'static str), + /// Store 下转型或执行器要求某种载荷而实际不符,携带预期诊断名称。 ObjectKindMismatch(&'static str), + /// 能力契约校验失败,定位能力、方法及不兼容原因。 TraitViolation { + /// 能力名称,用于定位失败契约。 capability: String, + /// 被核对的方法名称。 method: String, + /// 无需动态分配的具体失败原因。 reason: &'static str, }, + /// 方法替换不满足父级契约,定位定义、方法和原因。 OverrideViolation { + /// 方法所属类型定义名称。 definition: String, + /// 被核对的方法名称。 method: String, + /// 无需动态分配的具体失败原因。 reason: &'static str, }, + /// 类型解析无法接受注解名称;部分延后校验路径可暂缓此错误。 UnknownAnnotation(String), + /// 源码/原生签名或字段校验不匹配,携带调用位置与预期/实际类型诊断。 TypeMismatch { + /// 被校验函数或相关诊断主体。 function: String, + /// 参数、返回值或字段等发生不匹配的位置。 position: String, + /// 期望类型的可读名称。 expected: String, + /// 实际值类型的可读名称。 actual: String, }, + /// Store::cell_get 读取尚未初始化的闭包槽,不等于槽中存放 Nil。 EmptyCell, + /// 解码或代码校验拒绝指令,offset 是代码单元偏移,reason 是静态说明。 InvalidBytecode { + /// 字节码代码单元偏移,不是源文本行号。 offset: usize, + /// 无需动态分配的具体失败原因。 reason: &'static str, }, + /// 执行器读取/删除未绑定局部或相关捕获变量,携带变量名称。 UnboundLocal(String), + /// 执行器全局/内置查找或删除名称失败,携带名称。 NameNotFound(String), + /// 调用入口数量校验失败,记录函数名、最少/最多及实际位置参数数目。 ArgumentCount { + /// 被调用对象的诊断名称。 name: String, + /// 允许的最少位置参数数。 required: usize, + /// 允许的最多位置参数数。 maximum: usize, + /// 调用方实际提供的位置参数数。 provided: usize, }, + /// 执行帧、指令等预算或内部有界计数达到限制,携带具体限制类别。 ExecutionLimit(&'static str), } impl fmt::Display for VmError { + /// 宿主日志、UI 错误文本及标准 Error 展示调用;只借用错误,不消费其中的诊断数据。 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // 按变体生成稳定的可读诊断;结构化匹配应使用变体,不能依赖解析此文本。 match self { Self::AwaitFailed(message) => write!(f, "await failed: {message}"), Self::AwaitCancelled => f.write_str("await cancelled"), @@ -59,6 +101,7 @@ impl fmt::Display for VmError { Self::RefCountOverflow => f.write_str("object reference count exhausted"), Self::NoOwnedReference => f.write_str("no external strong reference to release"), Self::AttributeNotFound(name) => write!(f, "attribute not found: {name}"), + // 保留具体受限操作,便于区分协议缺失和只读上下文等限制。 Self::UnsupportedOperation(operation) => { write!(f, "object does not support {operation}") } @@ -69,6 +112,7 @@ impl fmt::Display for VmError { method, reason, } => write!(f, "trait {capability} method {method}: {reason}"), + // 名称来自注解解析,不把它伪装成已经解析的语言类型。 Self::UnknownAnnotation(name) => { write!(f, "unknown or invalid type annotation: {name}") } @@ -87,6 +131,7 @@ impl fmt::Display for VmError { "{function} {position} expects {expected}, received {actual}" ), Self::EmptyCell => f.write_str("cell is empty"), + // 偏移单位与指令解码器一致,避免把代码单元误报为源代码行号。 Self::InvalidBytecode { offset, reason } => { write!(f, "invalid bytecode at code unit {offset}: {reason}") } @@ -106,4 +151,5 @@ impl fmt::Display for VmError { } } +// 使宿主可通过标准 Error 接口传播/封装;当前未另提供嵌套 source 错误链。 impl std::error::Error for VmError {} -- 2.52.0 From b793bac40777e5cd6ca23b540a882c7e78f7755e Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:21:56 +0800 Subject: [PATCH 06/62] docs(objects): explain wordcode operations and codec boundaries --- crates/objects/src/instruction.rs | 98 +++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 13 deletions(-) diff --git a/crates/objects/src/instruction.rs b/crates/objects/src/instruction.rs index 0d3d17f..217d2ca 100644 --- a/crates/objects/src/instruction.rs +++ b/crates/objects/src/instruction.rs @@ -1,48 +1,87 @@ +//! 编译器与 VM 共享的指令协议:编译器构造逻辑指令,编码器产生双字节代码单元, +//! EndorCode 构造时解码并验证,eval 消费已解码指令;这里不执行指令或维护运行栈。 + use crate::{CompareOp, VmError}; -/// Endor wordcode, following CPython's opcode/oparg code-unit design. -/// Numeric opcode IDs are Endor's own, not a CPython bytecode ABI. +/// Endor 自有操作码;每个物理代码单元含 opcode/oparg 两字节。 +/// 采用类似 CPython 的单元设计,但编号与其字节码 ABI 不兼容。 #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] pub enum Opcode { + /// 无操作,无操作数。 Nop = 0, + /// 以操作数索引代码常量表,将借用常量保留为栈根。 LoadConst = 1, + /// 读取 localsplus 局部槽,操作数为槽索引。 LoadFast = 2, + /// 弹出栈顶写局部槽,执行器先处理对象赋值协议。 StoreFast = 3, + /// 清除局部槽并归还其根,未绑定时报错。 DeleteFast = 4, + /// 以 names 索引查全局空间,缺失再查函数捕获的内置空间。 LoadGlobal = 5, + /// 以 names 索引将栈顶写入全局字典并弹出。 StoreGlobal = 6, + /// 以 names 索引删除全局键,缺失时报错。 DeleteGlobal = 7, + /// 丢弃栈顶并释放对应根。 PopTop = 8, + /// 复制距栈顶第 argument 项(从 1 起),对象值新增栈根。 Copy = 9, + /// 交换栈顶与第 argument 项,合法深度至少为 2。 Swap = 10, + /// 压入调用协议专用 Null 标记,不是语言 Nil。 PushNull = 11, + /// 消费函数、接收者或 Null 及 argument 个显式参数,调用后留下结果。 Call = 12, + /// 移出帧结果并清理执行帧,无操作数。 ReturnValue = 13, + /// 从当前逻辑指令末尾向前跳 argument 个代码单元。 JumpForward = 14, + /// 从当前逻辑指令末尾向后跳 argument 个代码单元。 JumpBackward = 15, + /// 弹出条件,假时向前跳 argument 个代码单元。 PopJumpIfFalse = 16, + /// 弹出条件,真时向前跳 argument 个代码单元。 PopJumpIfTrue = 17, + /// 把栈顶经真值协议变为布尔值,无操作数。 ToBool = 18, + /// 对栈顶求真后逻辑取反,无操作数。 UnaryNot = 19, + /// 比较栈顶两个值;操作数采用 compare/compare_operation 的映射。 CompareOp = 20, + /// 读取 localsplus 对应 Cell 内值,空 Cell 报未绑定。 LoadDeref = 21, + /// 把栈顶写入对应 Cell,保持闭包共享槽身份。 StoreDeref = 22, + /// 清空对应 Cell,后续读取视为未绑定。 DeleteDeref = 23, + /// 压入对应 Cell 自身句柄,供创建嵌套闭包使用。 LoadClosure = 24, + /// 以 names 索引绑定栈顶对象属性,以所得值替换 owner 栈项。 LoadAttr = 25, + /// 以 names 索引把栈中的值写入对象属性。 StoreAttr = 26, + /// 消费 argument 对键值,构造插入顺序字典。 BuildMap = 27, + /// 操作数低/高 16 位分别计默认值与捕获槽数量。 MakeFunction = 28, + /// 消费四个构类输入并压入类型结果,无操作数。 BuildClass = 29, + /// 观察栈顶等待对象,Pending 保存暂停位置,Ready 替换为结果。 Await = 30, + /// 操作数指定算术种类,对两个栈值求值并压入结果。 BinaryArithmetic = 31, + /// 对栈顶数值取负,无操作数。 UnaryNegate = 32, + /// 编码器专用高位前缀;逻辑指令构造方不得直接提交。 ExtendedArg = 255, } impl Opcode { + /// 物理解码器验证一个操作码字节;未知编号返回 None,不做不安全 enum 转换。 fn decode(byte: u8) -> Option { + // 显式白名单保证保留编号不会误解为已实现指令。 Some(match byte { 0 => Self::Nop, 1 => Self::LoadConst, @@ -82,6 +121,7 @@ impl Opcode { }) } + /// 编解码共用的操作数分类;无操作数指令只能携带零且不得有扩展前缀。 pub(crate) fn takes_argument(self) -> bool { !matches!( self, @@ -98,25 +138,28 @@ impl Opcode { } } -/// A decoded logical instruction; EXTENDED_ARG is handled by the codec. +/// 编译器构造的逻辑指令;完整操作数为 u32,扩展前缀由编解码器处理。 #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Instruction { + /// 逻辑操作,不应手工指定 ExtendedArg。 pub opcode: Opcode, + /// 完整操作数;具体含义由 opcode 决定,构造时尚未做代码布局校验。 pub argument: u32, } impl Instruction { + /// 编译器/宿主组装指令;这里只赋值,合法索引和栈效果由 EndorCode 校验。 pub const fn new(opcode: Opcode, argument: u32) -> Self { Self { opcode, argument } } + /// 编译器发出无参数指令的便捷入口,把 argument 明确置零。 pub const fn simple(opcode: Opcode) -> Self { Self::new(opcode, 0) } - /// Build a function from [defaults..., closure cells..., code] on the stack. - /// The low 16 operand bits count trailing positional defaults; the high - /// 16 bits count cells in the code's free-variable order. No tuples or - /// subsequent mutation of the function object are required. + /// 编译器为栈布局 [默认值..., 捕获 Cell..., code] 生成函数创建指令。 + /// 低 16 位计末尾位置参数默认值,高 16 位计按自由变量顺序排列的捕获槽。 + /// VM 一次构造函数,不需要额外元组或事后修改函数字段。 pub const fn make_function(defaults: u16, closure: u16) -> Self { Self::new( Opcode::MakeFunction, @@ -124,6 +167,7 @@ impl Instruction { ) } + /// 编译器将比较运算转成固定操作数,与下方反向解码表配对。 pub const fn compare(operation: CompareOp) -> Self { Self::new( Opcode::CompareOp, @@ -138,29 +182,36 @@ impl Instruction { ) } - /// Decode a comparison operand; None denotes an invalid operand. + /// VM/校验器解析比较种类;超出六个固定值时返回 None。 pub fn compare_operation(argument: u32) -> Option { compare_operation(argument) } - /// Decode the default-value and closure-cell counts of MAKE_FUNCTION. + /// VM 创建函数及栈校验器拆出默认值数、闭包槽数,不访问运行栈。 pub fn function_counts(argument: u32) -> (usize, usize) { function_counts(argument) } - /// Encodes 8-bit operands directly and prefixes up to three EXTENDED_ARG - /// code units for larger operands. Each code unit is [opcode, operand]. + /// EndorCode::from_instructions 调用:小操作数直接编码,大操作数最多用三个前缀。 + /// 每单元两字节,前缀携带高位;不在这里重算跳转距离或校验常量/槽索引。 pub fn encode_all(instructions: &[Self]) -> Result, VmError> { + // 逐条追加;报错时局部缓冲丢弃,不向调用者返回部分编码。 let mut bytes = Vec::new(); + // 逻辑列表中每项最终对应一个非前缀单元及零到三个前缀单元。 for instruction in instructions { + // 前缀由完整 u32 操作数推导,拒绝手工插入导致分组歧义。 if instruction.opcode == Opcode::ExtendedArg { return Err(invalid(0, "EXTENDED_ARG is emitted by the encoder")); } + // 无参数操作携带非零数据是协议错误,报告当前输出的代码单元位置。 if !instruction.opcode.takes_argument() && instruction.argument != 0 { return Err(invalid(bytes.len() / 2, "unexpected instruction operand")); } + // 大端拆分方便先发高位前缀,最低字节始终放在最终 opcode 单元。 let parts = instruction.argument.to_be_bytes(); + // 跳过前导零;全零时选最后字节,保证仍发出一个有效单元。 let first = parts.iter().position(|part| *part != 0).unwrap_or(3); + // 只输出所需高位,最长三项,不把最低字节重复编码。 for part in &parts[first..3] { bytes.extend([Opcode::ExtendedArg as u8, *part]); } @@ -172,15 +223,18 @@ impl Instruction { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct DecodedInstruction { - /// Offsets count two-byte code units, including EXTENDED_ARG prefixes. + /// 逻辑指令起点(包括首个前缀),单位是双字节代码单元。 pub offset: usize, + /// 最后一个实际 opcode 单元之后的位置,作为相对跳转的基点。 pub next_offset: usize, + /// 已合并前缀的完整逻辑指令。 pub instruction: Instruction, } impl DecodedInstruction { - /// Resolve a jump relative to the end of this logical instruction. + /// VM/校验器相对逻辑指令末尾解析跳转;此处不检查目标是否为有效指令边界。 pub fn jump_target(self) -> Result { + // 非跳转操作不能借用跳转计算;条件跳转按前向距离解释。 if !matches!( self.instruction.opcode, Opcode::JumpForward @@ -194,37 +248,52 @@ impl DecodedInstruction { } } +/// EndorCode::with_layout 调用,将物理单元分组成逻辑指令;后续 verify 检查控制流与栈。 pub(crate) fn decode(bytes: &[u8]) -> Result, VmError> { + // 拒绝空代码与半个单元,保证下方 cursor+1 总能访问操作数字节。 if bytes.is_empty() || !bytes.len().is_multiple_of(2) { return Err(invalid( 0, "wordcode must contain complete two-byte code units", )); } + // 输出逻辑指令列表与物理字节游标分开,前缀不会独立进入输出。 let mut instructions = Vec::new(); + // cursor 以字节计,保存到 offset/next_offset 时再除以二。 let mut cursor = 0; + // 外层每次完整读一条逻辑指令,内层吸收连续高位前缀。 while cursor < bytes.len() { + // 记住首前缀/实际指令的起点,错误定位与跳转边界均用它。 let start = cursor / 2; + // 从高到低合并最多四个操作数字节。 let mut argument = 0_u32; + // 前缀最多三个,第四个将溢出 u32 编码容量。 let mut prefixes = 0; + // 遇到实际 opcode 才完成本条;流末尾仍只有前缀时报告悬空前缀。 loop { + // 前缀之后必须还有完整单元,不能将截断流视作正常结束。 if cursor >= bytes.len() { return Err(invalid(start, "dangling EXTENDED_ARG")); } + // 验证编号后再组合操作数,未知编号的错误位置指向该物理单元。 let opcode = Opcode::decode(bytes[cursor]) .ok_or_else(|| invalid(cursor / 2, "unknown opcode"))?; argument = (argument << 8) | u32::from(bytes[cursor + 1]); cursor += 2; + // 前缀只累积数据,不压入逻辑指令列表。 if opcode == Opcode::ExtendedArg { prefixes += 1; + // 在读取第五个数据字节之前拒绝,防截断高位。 if prefixes > 3 { return Err(invalid(start, "too many EXTENDED_ARG prefixes")); } continue; } + // 即使扩展数据全零,无参数指令也不允许前缀。 if !opcode.takes_argument() && (argument != 0 || prefixes != 0) { return Err(invalid(start, "unexpected instruction operand")); } + // 起点保留首前缀位置,末尾记录已消费单元之后的位置。 instructions.push(DecodedInstruction { offset: start, next_offset: cursor / 2, @@ -236,6 +305,7 @@ pub(crate) fn decode(bytes: &[u8]) -> Result, VmError> { Ok(instructions) } +/// Instruction 公共查询和代码校验共用的比较编号反向映射。 pub(crate) fn compare_operation(argument: u32) -> Option { Some(match argument { 0 => CompareOp::Lt, @@ -248,10 +318,12 @@ pub(crate) fn compare_operation(argument: u32) -> Option { }) } +/// 解出 MAKE_FUNCTION 的两个无符号计数;合法槽布局由 EndorCode/执行器另行检查。 pub(crate) fn function_counts(argument: u32) -> (usize, usize) { ((argument & 0xffff) as usize, (argument >> 16) as usize) } +/// 编解码与代码校验统一构造带代码单元偏移的错误,不把偏移转成源文件行号。 pub(crate) fn invalid(offset: usize, reason: &'static str) -> VmError { VmError::InvalidBytecode { offset, reason } } -- 2.52.0 From 6d8a5a8fe665ff3800b0a90955e8caffbff90344 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:24:34 +0800 Subject: [PATCH 07/62] docs(objects): explain executable layout and bytecode verification --- crates/objects/src/code.rs | 130 +++++++++++++++++++++++++++++++------ 1 file changed, 110 insertions(+), 20 deletions(-) diff --git a/crates/objects/src/code.rs b/crates/objects/src/code.rs index e63e1b9..6ffaf0f 100644 --- a/crates/objects/src/code.rs +++ b/crates/objects/src/code.rs @@ -1,3 +1,7 @@ +//! 编译器/宿主提交代码后在此构造不可变执行数据;core::frame 共享代码,eval 按偏移读取指令。 +//! 构造先验证名字布局、解码和控制流栈高度;这不是执行器,也不证明运行时值类型或调用一定成功。 +//! 常量中的对象句柄在 EndorCode 内只是表示,由堆上 EndorCodeObject 的 trace 建立托管强边。 + use std::any::Any; use std::collections::{BTreeSet, VecDeque}; use std::sync::Arc; @@ -6,39 +10,58 @@ use crate::builtins::trace_value; use crate::instruction::{compare_operation, decode, function_counts, invalid}; use crate::{DecodedInstruction, Instruction, ObjectPayload, ObjectRef, Opcode, Value, VmError}; -/// Local slots start with positional parameters. Captured locals occupy their -/// original slots as cells; free-variable cells follow all local slots. +/// 编译器生成、执行帧消费的槽布局:参数占局部槽前缀,捕获局部仍占原槽但存 Cell。 +/// 外部自由变量 Cell 追加在所有局部槽之后,不能与局部变量重名。 #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct CodeLayout { + /// 位置参数名称顺序,必须与 locals 前缀相同。 pub parameters: Vec, + /// 全部局部名称,参数必须是其完整前缀。 pub locals: Vec, + /// 需要提升为 Cell 的局部名称子集,不额外新增局部槽。 pub cell_variables: Vec, + /// 从外层捕获的 Cell 名称,顺序决定闭包传入槽顺序。 pub free_variables: Vec, + /// 全局与属性操作使用的文本名称索引表。 pub names: Vec, } -/// Source declaration metadata, separate from executable slot layout. +/// 编译器传入的源码类型声明,与执行槽布局分开;类型名称在调用/契约检查时解析。 #[derive(Debug, Clone, PartialEq, Eq)] pub struct SourceSignature { + /// 位置参数的类型注解顺序,数量必须与代码参数槽数相同。 pub parameters: Vec, + /// 非空的返回类型注解名称,尚不保证该名称可成功解析。 pub return_type: String, + /// 可选方法所属类型名,用于源码方法契约;Some 空串非法。 pub method_owner: Option, } +/// 已通过构造校验的 Rust 执行数据,尚未分配为托管代码对象;clone 不为常量对象增加根。 #[derive(Debug, Clone)] pub struct EndorCode { + /// 保留的物理双字节单元编码,含扩展前缀。 bytecode: Vec, + /// 解码后按偏移排序的逻辑指令列表,执行时无需重新组合前缀。 instructions: Vec, + /// 常量表示;Rust clone 不为其中的对象句柄 retain。 constants: Vec, + /// 经 validate_layout 检查的槽和名称布局。 layout: CodeLayout, + /// 从可达控制流计算的最大栈高度,供帧预留容量。 stack_size: usize, + /// 诊断显示的代码名称。 name: String, + /// 诊断显示的源码文件名,不读取该文件。 filename: String, + /// 调用方给出的起始行,供回溯显示,不是逐指令行号表。 first_line: usize, + /// 可选源码声明;不改变已校验的指令布局。 signature: Option, } impl EndorCode { + /// 宿主从原始字节码建立只有参数局部槽的代码;需要额外局部或本地 Cell 时用 with_layout。 pub fn new( bytecode: Vec, constants: Vec, @@ -57,6 +80,7 @@ impl EndorCode { ) } + /// 编译器与宿主把逻辑指令编码后走统一构造校验;此入口不替调用者修补跳转距离。 pub fn from_instructions( instructions: &[Instruction], constants: Vec, @@ -65,13 +89,17 @@ impl EndorCode { Self::with_layout(Instruction::encode_all(instructions)?, constants, layout) } + /// 全部构造入口汇合处:先检查布局,再解码,最后验证索引、跳转和可达路径栈高度。 pub fn with_layout( bytecode: Vec, constants: Vec, layout: CodeLayout, ) -> Result { + // 布局错误必须先拒绝,后续槽索引校验依赖这些不变量。 validate_layout(&layout)?; + // 合并 EXTENDED_ARG 并保留物理单元偏移,未知/截断编码在这里失败。 let instructions = decode(&bytecode)?; + // 校验成功后才建立可供执行器使用的代码,栈高只来自可达指令。 let stack_size = verify(&instructions, constants.len(), &layout)?; Ok(Self { bytecode, @@ -86,7 +114,9 @@ impl EndorCode { }) } + /// 编译器在代码构造后附加声明;这里只检查数量和非空字符串,不解析类型名。 pub fn with_source_signature(mut self, signature: SourceSignature) -> Result { + // 参数注解数量必须对应位置参数;每项与可选 owner 都不允许空串。 if signature.parameters.len() != self.parameters().len() || signature.return_type.is_empty() || signature.parameters.iter().any(String::is_empty) @@ -102,20 +132,20 @@ impl EndorCode { self.signature = Some(signature); Ok(self) } + /// Store 的源码调用/效果/契约检查借用声明;None 表示没有附加源码类型元数据。 pub fn source_signature(&self) -> Option<&SourceSignature> { self.signature.as_ref() } - /// Conservative proof over validated instructions. Local assignment is not - /// automatically pure: shared-reference assignment may update its old value. - /// Unknown calls and writes remain unknown, even when they could be safe for - /// a particular invocation. New opcodes require an explicit audit here. + /// Store/effects 的保守只读证明:所有指令(包括不可达部分)都须在白名单内。 + /// 局部赋值可能触发共享引用更新,不自动视为纯;未知调用/写入也不在这里证明安全。 pub fn is_statically_read_only(&self) -> bool { self.instructions .iter() .all(|decoded| read_only_opcode(decoded.instruction.opcode)) } + /// 编译器/宿主附加诊断位置;不校验文件存在或起始行范围,也不改变执行布局。 pub fn with_location( mut self, name: impl Into, @@ -128,55 +158,67 @@ impl EndorCode { self } + /// 宿主诊断/序列查看借用原编码;不允许绕过验证修改字节。 pub fn bytecode(&self) -> &[u8] { &self.bytecode } + /// eval、效果分析与诊断借用已解码逻辑指令。 pub fn instructions(&self) -> &[DecodedInstruction] { &self.instructions } + /// 执行器读取常量,代码载荷 trace 枚举其对象边;返回切片不新增托管根。 pub fn constants(&self) -> &[Value] { &self.constants } + /// 帧参数绑定与函数默认值校验读取参数顺序。 pub fn parameters(&self) -> &[String] { &self.layout.parameters } + /// 函数闭包校验和帧初始化读取捕获槽顺序。 pub fn free_variables(&self) -> &[String] { &self.layout.free_variables } + /// 编译器/帧与 Store 分析借用完整布局。 pub fn layout(&self) -> &CodeLayout { &self.layout } + /// 执行帧用可达路径最大栈高预留容量,不是执行步数上限。 pub fn stack_size(&self) -> usize { self.stack_size } + /// 错误和代码 repr 读取诊断名称。 pub fn name(&self) -> &str { &self.name } + /// 回溯与 repr 读取文件标识,不访问文件系统。 pub fn filename(&self) -> &str { &self.filename } + /// 回溯/诊断读取调用方设置的起始行。 pub fn first_line(&self) -> usize { self.first_line } + /// 帧创建 localsplus 时计算局部槽加外部自由变量槽的总数。 pub fn slot_count(&self) -> usize { self.layout.locals.len() + self.layout.free_variables.len() } - /// Whether a local/free slot holds a managed closure cell. + /// 帧初始化/闭包访问查询槽是否存 Cell,越界槽返回 false。 pub fn is_cell_slot(&self, slot: usize) -> bool { is_cell_slot(&self.layout, slot) } - /// Name of a local/free slot used by validated instructions. + /// 执行器为已验证槽生成未绑定变量等诊断时查询名称。 /// /// # Panics - /// Panics if the slot is outside this code's local/free-variable layout. + /// slot 超出局部/自由变量布局时 panic;调用者不能传入未经边界检查的槽。 pub fn slot_name(&self, slot: usize) -> &str { + // 局部槽保留原顺序,自由变量索引从局部槽末尾偏移。 if slot < self.layout.locals.len() { &self.layout.locals[slot] } else { &self.layout.free_variables[slot - self.layout.locals.len()] } } - /// Look up a logical instruction by its code-unit offset. + /// eval 按指令指针查询完整逻辑起点;非法偏移返回带位置的字节码错误。 pub fn instruction_at(&self, offset: usize) -> Result { instruction_index(&self.instructions, offset) .map(|index| self.instructions[index]) @@ -189,45 +231,52 @@ impl EndorCode { } } -/// Heap-allocated, immutable and shareable executable. Function objects hold a -/// managed reference to this object rather than embedding a copy of its code. +/// Store::new_code 分配的代码载荷;函数持有其托管引用,帧可共享内部不可变执行数据。 #[derive(Debug, Clone)] pub struct EndorCodeObject { + /// 共享不可变 Rust 执行数据;Arc 本身不是常量对象的托管根。 code: Arc, } impl EndorCodeObject { + /// Store::new_code 将已校验执行数据包装成待分配载荷,共享数据由 Arc 持有。 pub fn new(code: EndorCode) -> Self { Self { code: Arc::new(code), } } + /// Store 校验函数引用的代码布局时借用共享执行数据。 pub fn code(&self) -> &EndorCode { &self.code } - /// Share immutable executable data without copying constants or wordcode. - /// This Rust Arc does not retain heap objects referenced by constants; - /// callers must keep the managed code object rooted while using them. + /// core::frame 共享执行数据,不深拷贝常量和编码;Arc 不是常量托管对象的拥有根。 + /// 调用方使用常量时仍须保持托管代码对象有根,不能只靠此 Arc 延长常量对象寿命。 pub fn shared_code(&self) -> Arc { Arc::clone(&self.code) } } impl ObjectPayload for EndorCodeObject { + /// 不可变代码载荷不实现原地赋值,允许只读局部引用重绑定。 fn reference_assignment_only(&self) -> bool { true } + /// Store::payload 的 Rust 下转型入口。 fn as_any(&self) -> &dyn Any { self } + /// 代码载荷的诊断类别,不是名义类型判断。 fn kind_name(&self) -> &'static str { "code" } + /// Heap 分配/替换代码载荷时登记常量对象边;不递归扫描其指令或源码文本。 fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { + // 仅对象变体贡献边,重复出现的同一对象仍逐次计数。 for value in self.code.constants() { trace_value(value, visit); } } + /// Store::repr 用代码名/文件/起始行生成诊断,不运行代码。 fn repr(&self, _identity: ObjectRef) -> Result { Ok(format!( "", @@ -238,7 +287,8 @@ impl ObjectPayload for EndorCodeObject { } } -// Shared by the local proof and call-graph analysis. Unknown opcodes fail closed. +// 白名单之外一律不在此处证明只读;动态调用图分析可以另行判断已知调用。 +/// 本地只读证明和 effects 调用图分析共享白名单;新增指令需显式审查其副作用。 pub(crate) fn read_only_opcode(opcode: Opcode) -> bool { matches!( opcode, @@ -269,7 +319,9 @@ pub(crate) fn read_only_opcode(opcode: Opcode) -> bool { ) } +/// with_layout 在解码前调用,验证各名称表内部唯一性以及局部/捕获槽的交叉关系。 fn validate_layout(layout: &CodeLayout) -> Result<(), VmError> { + // 每类名称表都检查内部唯一性,不能把全部表混在一个 seen 中。 for names in [ &layout.parameters, &layout.locals, @@ -277,8 +329,11 @@ fn validate_layout(layout: &CodeLayout) -> Result<(), VmError> { &layout.free_variables, &layout.names, ] { + // 每张表单独去重;不同表间允许的重名关系在后面单独检查。 let mut seen = BTreeSet::new(); + // 空名称无法可靠绑定,表内重复会使槽或名称索引产生歧义。 for name in names { + // 发现首个违规就退出,构造不产生可执行半成品。 if name.is_empty() || !seen.insert(name) { return Err(VmError::InvalidArgument( "empty or duplicate code variable name", @@ -286,11 +341,13 @@ fn validate_layout(layout: &CodeLayout) -> Result<(), VmError> { } } } + // 帧按局部槽前缀绑定参数,不能仅检查包含关系。 if !layout.locals.starts_with(&layout.parameters) { return Err(VmError::InvalidArgument( "parameter names must lead local slots", )); } + // Cell 局部必须来自 locals;外部自由变量不得同时作为本地变量。 if layout .cell_variables .iter() @@ -307,22 +364,29 @@ fn validate_layout(layout: &CodeLayout) -> Result<(), VmError> { Ok(()) } +/// 代码公共查询与操作数校验共用:外部自由变量槽全是 Cell,局部槽按名称判断。 fn is_cell_slot(layout: &CodeLayout, slot: usize) -> bool { + // 自由变量段范围内都为 Cell;段外返回 false,避免越界索引。 if slot >= layout.locals.len() { slot < layout.locals.len() + layout.free_variables.len() } else { + // 局部槽按名字判断是否提升为 Cell,不改变其原有位置。 layout.cell_variables.contains(&layout.locals[slot]) } } +/// instruction_at、跳转验证与栈分析用二分查找逻辑起点;前缀中段不是合法目标。 fn instruction_index(instructions: &[DecodedInstruction], offset: usize) -> Option { instructions .binary_search_by_key(&offset, |instruction| instruction.offset) .ok() } +/// DecodedInstruction 与 verify 共用相对偏移计算;此助手只算数,不验证目标边界。 pub(crate) fn jump_target(instruction: DecodedInstruction) -> Result { + // 操作数是代码单元距离,不是字节数或逻辑指令数量。 let argument = instruction.instruction.argument as usize; + // 仅 JumpBackward 做减法,条件跳转与 JumpForward 做加法;checked 防止绕回。 let target = if instruction.instruction.opcode == Opcode::JumpBackward { instruction.next_offset.checked_sub(argument) } else { @@ -331,17 +395,20 @@ pub(crate) fn jump_target(instruction: DecodedInstruction) -> Result Result { + // 操作数检查覆盖全部指令,包括入口不可达部分。 for instruction in instructions { + // 逻辑操作码与完整 u32 参数;前缀已由解码器合并。 let Instruction { opcode, argument } = instruction.instruction; + // 表索引以 usize 使用;其上界由对应布局或常量数量限定。 let index = argument as usize; + // Fast 不能指向 Cell,Deref 必须指向 Cell;跳转必须落在完整逻辑起点。 let valid = match opcode { Opcode::LoadConst => index < constants, Opcode::LoadFast | Opcode::StoreFast | Opcode::DeleteFast => { @@ -368,6 +435,7 @@ fn verify( Opcode::ExtendedArg => false, _ => true, }; + // 将错误定位到指令起点,尚未开始传播栈状态。 if !valid { return Err(invalid( instruction.offset, @@ -375,15 +443,24 @@ fn verify( )); } } + // None 表示尚未从入口到达,不表示其操作数未检查。 let mut heights = vec![None; instructions.len()]; + // decode 保证列表非空;代码入口求值栈为空。 heights[0] = Some(0_usize); + // 工作队列保存待展开的指令索引,每个新到达位置只加入一次。 let mut pending = VecDeque::from([0]); + // 全部可达指令执行前后的最大栈高度。 let mut maximum = 0; + // 前向/回向边共用工作队列,汇合高度一致时不重复展开。 while let Some(index) = pending.pop_front() { + // 队列只保存已赋高度的位置,因此下方 unwrap 由入队不变量保证。 let instruction = instructions[index]; + // 当前指令执行前高度,不含任何类型或值内容信息。 let height = heights[index].unwrap(); + // 逻辑操作码与完整 u32 参数;前缀已由解码器合并。 let Instruction { opcode, argument } = instruction.instruction; let argument = argument as usize; + // required 是最深访问需求,popped/pushed 是净变化来源;Copy/Swap 不弹出其深层项。 let (required, popped, pushed) = match opcode { Opcode::LoadConst | Opcode::LoadFast @@ -407,19 +484,23 @@ fn verify( Opcode::Copy => (argument, 0, 1), Opcode::Swap => (argument, 0, 0), Opcode::Call => { + // 显式参数之外还消费函数与接收者/Null 两项,先检查加法溢出。 let consumed = argument .checked_add(2) .ok_or_else(|| invalid(instruction.offset, "call operand overflow"))?; (consumed, consumed, 1) } Opcode::BuildMap => { + // 每对键值占两项,先检查乘法溢出。 let consumed = argument .checked_mul(2) .ok_or_else(|| invalid(instruction.offset, "map operand overflow"))?; (consumed, consumed, 1) } Opcode::MakeFunction => { + // 函数创建消费默认值、捕获 Cell 和一个代码对象,压回一个函数。 let (defaults, closure) = function_counts(instruction.instruction.argument); + // 两个计数来自 16 位段,总和加代码对象项有界。 let consumed = defaults + closure + 1; (consumed, consumed, 1) } @@ -427,17 +508,21 @@ fn verify( Opcode::ReturnValue => (1, 1, 0), _ => (0, 0, 0), }; + // 在减 popped 前检查足够高度,避免栈下溢。 if height < required { return Err(invalid(instruction.offset, "evaluation stack underflow")); } + // 返回必须恰好留下一个结果供移出,禁止帧内遗留其他栈项。 if opcode == Opcode::ReturnValue && height != 1 { return Err(invalid( instruction.offset, "RETURN_VALUE must leave an empty frame stack", )); } + // 校验保证减法安全;随后统计当前和后继高度。 let next_height = height - popped + pushed; maximum = maximum.max(height).max(next_height); + // 返回无后继;条件跳转同时传播跳转/顺序两条路径,不猜测条件值。 let successors: Vec = match opcode { Opcode::ReturnValue => Vec::new(), Opcode::JumpForward | Opcode::JumpBackward => vec![jump_target(instruction)?], @@ -446,11 +531,15 @@ fn verify( } _ => vec![instruction.next_offset], }; + // 每条后继都必须指向现存逻辑指令,顺序落到代码末尾也属于错误。 for successor in successors { + // 将物理单元偏移转换为逻辑指令索引,拒绝扩展前缀中部。 let target = instruction_index(instructions, successor).ok_or_else(|| { invalid(instruction.offset, "execution falls off the code object") })?; + // 已到达位置只能接受同高;新位置设置高度并排队。 match heights[target] { + // 回边或分支汇合必须与第一次到达时的高度完全一致。 Some(existing) if existing != next_height => { return Err(invalid( successor, @@ -458,6 +547,7 @@ fn verify( )); } Some(_) => {} + // 首次到达的后继写入高度,之后排队检查它的栈需求。 None => { heights[target] = Some(next_height); pending.push_back(target); -- 2.52.0 From fce5ccf88367e377b842e4ed250ed475d2afad48 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:27:15 +0800 Subject: [PATCH 08/62] docs(objects): explain native contracts and scoped root transfers --- crates/objects/src/native.rs | 118 ++++++++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) diff --git a/crates/objects/src/native.rs b/crates/objects/src/native.rs index 44c1746..f85e0cb 100644 --- a/crates/objects/src/native.rs +++ b/crates/objects/src/native.rs @@ -1,3 +1,7 @@ +//! 宿主原生调用协议:Store 校验声明,VM 分派调用,NativeContext 管理临时根与输出所有权。 +//! 返回 Invoke 把调用交回 VM 的帧循环,避免 Rust 回调递归进入求值器。 +//! ReadOnly 是宿主承诺加托管写屏障;本模块不能拦截任意 Rust 回调自行执行的外部 I/O。 + use std::{fmt, sync::Arc}; use crate::{ObjectPayload, ObjectRef, ObjectStore, Value, VmError, WeakObjectRef}; @@ -6,7 +10,9 @@ use crate::{ObjectPayload, ObjectRef, ObjectStore, Value, VmError, WeakObjectRef /// callable through the VM's existing frame loop, without recursively entering it. #[derive(Debug)] pub enum NativeResult { + /// 回调内为借用结果;execute 成功输出时其中对象已获得移交给 VM 的根。 Return(Value), + /// 委托目标与参数在 execute 成功输出时各有拥有根,VM 接管后负责释放。 Invoke { function: ObjectRef, arguments: Vec, @@ -17,27 +23,36 @@ pub enum NativeResult { /// The store validates metadata before use; usize::MAX denotes an open upper bound. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct CallArity { + /// 最少位置参数数量。 pub required: usize, + /// 最多位置参数数量,usize::MAX 表示开放上界。 pub maximum: usize, } impl CallArity { + /// 宿主声明固定位置参数数量,含显式接收者。 pub const fn exact(count: usize) -> Self { Self::range(count, count) } + /// 宿主声明闭区间,构造时不验证上下界;使用前 validate。 pub const fn range(required: usize, maximum: usize) -> Self { Self { required, maximum } } + /// VM/契约检查查询数量是否落在闭区间,不改变元数据。 pub fn accepts(self, count: usize) -> bool { (self.required..=self.maximum).contains(&count) } + /// Store/调用入口拒绝颠倒的上下界;成功返回原数量声明。 pub(crate) fn validate(self) -> Result { + // 上界不能小于下界,否则任何实际调用都无法满足有效区间。 if self.required > self.maximum { return Err(VmError::InvalidArgument("invalid callable arity metadata")); } Ok(self) } + /// 调用入口先验证声明,再生成携带函数名和实参数量的错误。 pub(crate) fn check(self, name: String, provided: usize) -> Result<(), VmError> { self.validate()?; + // 元数据合法后再区分用户提供的数量不匹配。 if !self.accepts(provided) { return Err(VmError::ArgumentCount { name, @@ -54,11 +69,14 @@ impl CallArity { /// existing objects or perform external I/O. Unknown legacy calls remain unknown. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CallEffect { + /// 可分配结果,但承诺不改已有对象或执行外部 I/O。 ReadOnly, /// The native body itself is read-only; delegated effects are dynamic and /// checked by the VM in the same execution/heap scope. Delegated, + /// 声明可能修改已有托管对象,只读执行不允许。 Mutating, + /// 声明包含外部效果,只读执行不允许。 External, } @@ -66,7 +84,9 @@ pub enum CallEffect { /// all definition edges even when an earlier alternative already matches. #[derive(Debug, Clone)] pub enum CallType { + /// 单个已解析注解,不是源码字符串。 Single(crate::AnnotationType), + /// 平坦非空候选列表;所有定义引用都须有效。 Union(Vec), } impl From for CallType { @@ -75,31 +95,40 @@ impl From for CallType { } } impl CallType { + /// 宿主构造类型或 Unit 的平坦联合,供可空参数/返回值约束使用。 pub fn nullable(value: crate::AnnotationType) -> Self { Self::Union(vec![value, crate::AnnotationType::Unit]) } + /// 内部检查和契约分析将单类型与联合统一为借用切片。 pub(crate) fn alternatives(&self) -> &[crate::AnnotationType] { + // 单项通过 from_ref 借用,联合直接借用 Vec,不额外分配或复制定义引用。 match self { Self::Single(value) => std::slice::from_ref(value), Self::Union(values) => values, } } + /// 载荷 trace/调用保护枚举所有名义类型与能力引用,不因提前匹配而漏边。 pub fn references(&self) -> impl Iterator + '_ { self.alternatives() .iter() .copied() .filter_map(crate::AnnotationType::reference) } + /// 契约分析检查联合是否含 Any,不代表其他分支的定义边可省略。 pub fn is_any(&self) -> bool { self.alternatives() .iter() .any(|value| matches!(value, crate::AnnotationType::Any)) } + /// 调用/签名检查确认联合非空,且每个托管类型/能力引用有效并有正确载荷。 pub fn validate(&self, store: &ObjectStore) -> Result<(), VmError> { + // 空联合没有可接受值,必须作为声明错误拒绝。 if self.alternatives().is_empty() { return Err(VmError::InvalidArgument("empty native union type")); } + // 即使首个分支是 Any,也校验后续全部定义,不允许无效句柄藏在联合中。 for value in self.alternatives() { + // 只有名义/能力注解包含定义句柄,其他直接注解没有需验证的对象。 match *value { crate::AnnotationType::Nominal(reference) => { store.payload::(reference, "Type")?; @@ -112,6 +141,7 @@ impl CallType { } Ok(()) } + /// 参数/返回检查先验证全部分支,再按顺序尝试匹配;全不匹配才生成类型错误。 pub fn check( &self, store: &ObjectStore, @@ -120,11 +150,14 @@ impl CallType { value: &Value, ) -> Result<(), VmError> { self.validate(store)?; + // 通过完整声明校验后才允许按首个匹配项短路。 for expected in self.alternatives() { + // 匹配即成功;协议失败仍传播错误,而非当作不匹配。 if store.matches_annotation(value, *expected)? { return Ok(()); } } + // 所有项均不匹配时才组装诊断类型名,不在成功热路径构造文本。 let names = self .alternatives() .iter() @@ -143,13 +176,19 @@ impl CallType { /// Missing fixed slots are covered by variadic; optionality comes from arity. #[derive(Debug, Clone)] pub struct CallSignature { + /// 参数数量区间决定可省略的尾部参数,不由类型列表长度直接决定。 pub arity: CallArity, + /// 固定位置参数约束,含需要显式传入的接收者。 pub parameters: Vec, + /// 固定列表之外的参数统一使用此约束。 pub variadic: Option, + /// 直接/委托完成后应检查的结果约束。 pub returns: CallType, + /// 回调效果声明,供只读许可与方法替换契约判断。 pub effect: CallEffect, } impl CallSignature { + /// 宿主构造固定参数签名,不含可变参数;传入类型尚需使用前验证。 pub fn exact( parameters: Vec, returns: crate::AnnotationType, @@ -163,6 +202,7 @@ impl CallSignature { effect, } } + /// 载荷 trace 和临时保护枚举固定、可变及返回类型中的全部托管引用。 pub fn references(&self) -> impl Iterator + '_ { self.parameters .iter() @@ -170,13 +210,16 @@ impl CallSignature { .chain([&self.returns]) .flat_map(CallType::references) } + /// Store/执行入口校验数量区间和类型覆盖完整性,并验证全部定义引用。 pub fn validate(&self, store: &ObjectStore) -> Result<(), VmError> { self.arity.validate()?; + // 没有可变约束时固定类型列表必须覆盖最大参数数;有可变约束时不能超出上界。 if self.parameters.len() > self.arity.maximum || (self.variadic.is_none() && self.parameters.len() != self.arity.maximum) { return Err(VmError::InvalidArgument("incomplete native signature")); } + // 参数、可变项与返回项的定义引用都校验,不只验证本次实际传入的参数。 for expected in self .parameters .iter() @@ -187,6 +230,7 @@ impl CallSignature { } Ok(()) } + /// 原生调用进入回调前校验数量、效果许可和逐项类型,包括显式接收者。 pub fn check_arguments( &self, store: &ObjectStore, @@ -195,6 +239,7 @@ impl CallSignature { ) -> Result<(), VmError> { self.validate(store)?; self.arity.check(name.into(), arguments.len())?; + // Delegated 只保证宿主本体只读,被委托目标的效果由 VM 在相同作用域继续检查。 if store.is_read_only() && !matches!(self.effect, CallEffect::ReadOnly | CallEffect::Delegated) { @@ -202,7 +247,9 @@ impl CallSignature { "native effect is forbidden during read-only execution", )); } + // 按位置选择固定约束,超出固定部分才回退到可变约束。 for (index, value) in arguments.iter().enumerate() { + // 数量合法不代表类型覆盖完整,因此缺少约束仍作为声明错误。 let expected = self .parameters .get(index) @@ -212,6 +259,7 @@ impl CallSignature { } Ok(()) } + /// 原生调用返回后校验结果类型;不重新验证整份参数元数据。 pub fn check_return( &self, store: &ObjectStore, @@ -222,6 +270,7 @@ impl CallSignature { } } +/// 宿主实现接口;VM 克隆分派器 Arc 后调用 invoke,不持有载荷借用跨可变 Store 操作。 pub trait NativeCallable: fmt::Debug + Send + Sync + std::any::Any { /// Immutable resolved types; referenced definitions must be traced by the /// payload. ContextNativeFunction snapshots and traces them automatically. @@ -234,6 +283,7 @@ pub trait NativeCallable: fmt::Debug + Send + Sync + std::any::Any { self.signature().map(|signature| signature.arity) } + /// 宿主在临时根作用域中执行;参数借用,结果可直接返回或委托 VM 调用其他函数。 fn invoke( &self, context: &mut NativeContext<'_>, @@ -245,8 +295,11 @@ pub trait NativeCallable: fmt::Debug + Send + Sync + std::any::Any { /// are temporary owned roots. Return/Invoke promotes only its output roots; /// Drop releases everything else, including on error or unwinding. pub struct NativeContext<'a> { + // 整次回调独占借用存储,提供受控修改与查询。 store: &'a mut ObjectStore, + // 本作用域拥有的根清单,重复身份代表多份拥有根,不能去重。 roots: Vec, + // 当前调用函数的借用身份,execute 另外将其加入保护根。 callee: ObjectRef, } @@ -256,6 +309,7 @@ impl<'a> NativeContext<'a> { self.callee } + /// 宿主经 Store 附着不可替换的名义类型,沿用只读屏障。 pub fn attach_type( &mut self, object: ObjectRef, @@ -264,6 +318,7 @@ impl<'a> NativeContext<'a> { self.store.attach_type(object, type_object) } + /// 宿主注册能力,经 Store 校验,不直接改类型表。 pub fn implement_trait( &mut self, definition: ObjectRef, @@ -272,27 +327,33 @@ impl<'a> NativeContext<'a> { self.store.implement_trait(definition, capability) } + /// 宿主只读查询存储,修改使用上下文受控入口。 pub fn store(&self) -> &ObjectStore { self.store } + /// 分配载荷并登记临时拥有根;未作为输出移交时由 Drop 释放。 pub fn allocate(&mut self, payload: impl ObjectPayload) -> Result { + // 只有分配/升级成功后才登记根,错误路径不留下虚假的释放条目。 let object = self.store.allocate(payload)?; self.roots.push(object); Ok(object) } + /// 创建 Record,经 allocate 统一登记临时根。 pub fn new_object(&mut self) -> Result { self.allocate(crate::Record) } /// Build a language function under this native scope using validated code. + /// 从代码创建无默认值/无捕获槽的源码函数,并登记临时根。 pub fn new_function( &mut self, name: impl Into, code: ObjectRef, globals: ObjectRef, ) -> Result { + // Store 校验代码布局和命名空间;这里不自动创建默认值或捕获槽。 let function = self .store .new_function(name, code, globals, vec![], vec![])?; @@ -300,18 +361,23 @@ impl<'a> NativeContext<'a> { Ok(function) } + /// 创建空字典并登记其外部根。 pub fn new_dict(&mut self) -> Result { + // 只有分配/升级成功后才登记根,错误路径不留下虚假的释放条目。 let object = self.store.new_dict()?; self.roots.push(object); Ok(object) } + /// 创建空/已初始化 Cell 并登记外部根。 pub fn new_cell(&mut self, value: Option) -> Result { + // 只有分配/升级成功后才登记根,错误路径不留下虚假的释放条目。 let object = self.store.new_cell(value)?; self.roots.push(object); Ok(object) } + /// 属性写入交给 Store/Heap 维护强边、类型约束和写屏障。 pub fn set_attr( &mut self, object: ObjectRef, @@ -321,22 +387,27 @@ impl<'a> NativeContext<'a> { self.store.set_attr(object, name, value) } + /// 属性删除交给 Store 校验,成功释放旧字段强边。 pub fn del_attr(&mut self, object: ObjectRef, name: &str) -> Result<(), VmError> { self.store.del_attr(object, name) } + /// 受控字典写入,保持容器身份和引用计数。 pub fn dict_set(&mut self, object: ObjectRef, key: Value, value: Value) -> Result<(), VmError> { self.store.dict_set(object, key, value) } + /// 受控删除字典键;缺键返回 false,协议失败另行报错。 pub fn dict_delete(&mut self, object: ObjectRef, key: &Value) -> Result { self.store.dict_delete(object, key) } + /// 受控更新共享 Cell,先保留新边再释放旧边。 pub fn cell_set(&mut self, object: ObjectRef, value: Value) -> Result<(), VmError> { self.store.cell_set(object, value) } + /// 替换载荷并重建边快照,保持对象身份。 pub fn replace_payload( &mut self, object: ObjectRef, @@ -345,15 +416,20 @@ impl<'a> NativeContext<'a> { self.store.replace_payload(object, payload) } + /// 升级成功的弱引用根登记为临时根;过期 None 不登记。 pub fn upgrade(&mut self, weak: WeakObjectRef) -> Result, VmError> { + // 只有分配/升级成功后才登记根,错误路径不留下虚假的释放条目。 let object = self.store.upgrade(weak)?; + // 已过期弱引用不产生根,成功升级的一份根交给 Drop 管理。 if let Some(object) = object { self.roots.push(object); } Ok(object) } + /// 给借用对象 retain 一份临时根;直接值不需要托管计数。 fn protect(&mut self, value: &Value) -> Result<(), VmError> { + // retain 先成功才入清单,避免失败时 Drop 多释放一份根。 if let Value::Object(object) = value { self.store.retain(*object)?; self.roots.push(*object); @@ -363,58 +439,77 @@ impl<'a> NativeContext<'a> { /// Internal execution bridge. Every returned object reference owns a root; /// consumers must release Return values or all Invoke arguments/function. + /// VM 执行桥:校验/保护输入,执行回调,验证直接结果,最后移交输出根。 pub fn execute( + // 整次回调独占借用存储,提供受控修改与查询。 store: &'a mut ObjectStore, function: ObjectRef, arguments: &[Value], + // 可共享回调分派器;执行前克隆 Arc,避免持有载荷借用。 callable: Arc, ) -> Result { + // 执行回调前检查分派器声明的数量约束。 if let Some(arity) = callable.arity() { arity.check( store.object(function)?.payload().kind_name().into(), arguments.len(), )?; } + // 从载荷快照取得完整签名,与 callable 的动态返回值分开。 let signature = store.native_signature(function)?; + // 诊断名在可变借用上下文建立前复制,避免跨调用持有载荷引用。 let name = store.object(function)?.payload().kind_name().to_owned(); + // 可选完整签名存在时才校验/保护;缺失元数据保留既有调用协议。 if let Some(signature) = &signature { signature.check_arguments(store, &name, arguments)?; } + // 首参为对象时查询接收者的方法契约及应保持的返回约束。 let trait_returns = if let Some(Value::Object(receiver)) = arguments.first() { store.check_method_contract_call(function, *receiver, false)?; store.method_trait_returns(function, *receiver)? } else { Vec::new() }; + // 从这里起所有已登记根由 RAII Drop 清理,错误和展开路径同样有效。 let mut context = Self { store, roots: Vec::new(), callee: function, }; context.protect(&Value::Object(function))?; + // 可选完整签名存在时才校验/保护;缺失元数据保留既有调用协议。 if let Some(signature) = &signature { for reference in signature.references() { context.protect(&Value::Object(reference))?; } } + // 能力返回约束也可能持有类型定义,执行回调时必须保持其存活。 for (_, expected) in &trait_returns { + // 每条声明引用独立保护,重复边对应多份根并会逐次释放。 for reference in expected.references() { context.protect(&Value::Object(reference))?; } } + // 输入每个对象参数取得临时根,防回调删除原持有边后参数失效。 for argument in arguments { context.protect(argument)?; } + // 回调错误直接传播,context 自动释放输入保护和回调内创建的临时对象根。 let result = callable.invoke(&mut context, arguments)?; + // 直接结果立即检查;Invoke 的最终结果由 VM 的委托返回检查继续处理。 if let (Some(signature), NativeResult::Return(value)) = (&signature, &result) { signature.check_return(context.store(), &name, value)?; } + // 直接结果还须满足接收者能力方法要求,不只满足原生自身签名。 if let NativeResult::Return(value) = &result { + // 每个能力的返回约束都要满足,不能在第一个成功后跳过其他能力。 for (name, expected) in &trait_returns { expected.check(context.store(), name, "return".into(), value)?; } } + // 记录移交边界;新增输出根验证成功后才从清单中移走。 let transferred_from = context.roots.len(); + // 给每个输出位置 retain;中途失败则清单仍完整,由 Drop 回收已取得的输出根。 match &result { NativeResult::Return(value) => context.protect(value)?, NativeResult::Invoke { @@ -422,19 +517,22 @@ impl<'a> NativeContext<'a> { arguments, } => { context.protect(&Value::Object(*function))?; + // 每个委托参数取得待移交的输出根,重复对象参数也逐项保留。 for argument in arguments { context.protect(argument)?; } } } - // Transfer exactly the output roots; all other roots remain scoped. + // truncate 只移走输出根的清单条目,不 release;调用者接管这些拥有根,其他根仍由 Drop 释放。 context.roots.truncate(transferred_from); Ok(result) } } impl Drop for NativeContext<'_> { + /// 作用域结束、回调错误或栈展开时回收全部未移交拥有根。 fn drop(&mut self) { + // 按取得的反序归还所有未移交根;每项代表一份所有权,不能按身份去重。 for root in self.roots.drain(..).rev() { self.store .release(root) @@ -446,9 +544,13 @@ impl Drop for NativeContext<'_> { /// A context-aware function with explicit, immutable captured object edges. #[derive(Debug)] pub struct ContextNativeFunction { + // 宿主静态诊断名,不承担语言类型身份。 name: &'static str, + // 可共享回调分派器;执行前克隆 Arc,避免持有载荷借用。 callable: Arc, + // 显式不可变托管捕获边,由 trace 逐条登记。 captures: Vec, + // 构造时从 callable 快照,作为不可变元数据并参与 trace。 signature: Option, } @@ -456,14 +558,19 @@ impl ContextNativeFunction { /// Borrow the concrete host implementation for intrinsic dispatch checks. /// Function identity or a mutable class-table slot alone is not proof that /// its current payload still implements a host intrinsic. + /// 宿主内建分派借用具体回调实现;类型不匹配返回 None,不以函数身份猜测实现。 pub fn implementation(&self) -> Option<&T> { + // 仅做 Rust Any 下转型,不执行回调,不检查语言层名义类型。 let implementation: &dyn std::any::Any = self.callable.as_ref(); implementation.downcast_ref() } + /// 宿主包装上下文回调,快照签名与显式捕获边,随后由 Store 分配。 pub fn new( + // 宿主静态诊断名,不承担语言类型身份。 name: &'static str, callable: impl NativeCallable, + // 显式不可变托管捕获边,由 trace 逐条登记。 captures: Vec, ) -> Self { Self { @@ -476,31 +583,40 @@ impl ContextNativeFunction { } impl ObjectPayload for ContextNativeFunction { + // Store 分配时可附着 Function 类型;可调用性和函数分类分开。 fn is_function(&self) -> bool { true } + // 本载荷无原地赋值实现,只读调用可以直接重绑定局部引用。 fn reference_assignment_only(&self) -> bool { true } + // Store/宿主 intrinsic 分派所需的载荷下转型入口。 fn as_any(&self) -> &dyn std::any::Any { self } + // 使用宿主配置的静态名生成诊断。 fn kind_name(&self) -> &'static str { self.name } + // Heap 快照签名定义边及显式捕获边;不能在分配后私下修改这些引用。 fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { + // 签名中的参数、可变参数和返回约束定义也属于载荷拥有边。 if let Some(signature) = &self.signature { for reference in signature.references() { visit(reference); } } + // 每个捕获项都拥有独立强边,包括重复捕获。 for capture in &self.captures { visit(*capture); } } + // Store 查询构造时的签名快照,clone 不新增托管根。 fn call_signature(&self) -> Option { self.signature.clone() } + // VM 克隆回调 Arc 后释放载荷借用,再创建可变 NativeContext。 fn native_callable(&self) -> Option> { Some(self.callable.clone()) } -- 2.52.0 From c25cb6756ef94f29a3d5b2b8e9010d6dd56c86ba Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:30:28 +0800 Subject: [PATCH 09/62] docs(objects): explain numeric representations and operation budgets --- crates/objects/src/number.rs | 109 +++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/crates/objects/src/number.rs b/crates/objects/src/number.rs index 1e9449f..3d2bd80 100644 --- a/crates/objects/src/number.rs +++ b/crates/objects/src/number.rs @@ -1,3 +1,5 @@ +//! 编译器字面量、VM 算术比较与宿主数值转换共用的表示;构造保存与运算预算分开。 +//! Eq 比较存储表示,numeric_compare 比较数学值;Rust 数值载荷不持有托管对象边。 //! Lossless number representations. Arithmetic and numeric equivalence are //! deliberately separate from representation equality; real operations preserve //! exact values unless a binary-float operand explicitly selects float mode. @@ -9,17 +11,23 @@ use crate::{CompareOp, ObjectPayload, ObjectRef, VmError}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct EndorInteger { + /// 规范化整数符号,零为 false。 negative: bool, + /// 无符号、无前导零的数字串;零保留单个 0。 digits: String, } impl EndorInteger { /// Decimal digits of any length, limited only by available memory. + /// 编译器/宿主解析字面量,构造可保留长数字,实际算术再执行预算检查。 pub fn parse(text: &str) -> Result { + // 拆符号后校验数字,空串或只有符号均非法。 let (negative, digits) = split_sign(text); + // 只允许非空 ASCII 数字,不接受空白、下划线或进制前缀。 if !is_digits(digits) { return Err(VmError::InvalidArgument("invalid integer")); } + // 去前导零,全零统一保存单个正零。 let digits = digits.trim_start_matches('0'); Ok(Self { negative: negative && !digits.is_empty(), @@ -27,21 +35,27 @@ impl EndorInteger { }) } + /// 宿主/转换借用规范化整数绝对值数字串。 pub fn digits(&self) -> &str { &self.digits } + /// 查询整数符号,构造已消除负零。 pub fn is_negative(&self) -> bool { self.negative } + /// 数值真值/分母检查使用的零判断,具体实现按表示处理。 pub fn is_zero(&self) -> bool { self.digits == "0" } + /// 算术快路径尝试收窄整数,超范围返回 None,不截断。 pub fn to_i64(&self) -> Option { + // 先读 u64 幅值以容纳 i64::MIN 的绝对值,再检查有符号范围。 let magnitude = self.digits.parse::().ok()?; if self.negative { + // 最小负数的幅值比最大正数多一,必须单独处理。 if magnitude == (i64::MAX as u64) + 1 { Some(i64::MIN) } else { @@ -54,6 +68,7 @@ impl EndorInteger { } impl fmt::Display for EndorInteger { + /// Display/宿主诊断按当前表示输出,精确表示不经二进制浮点中转。 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.negative { f.write_str("-")?; @@ -64,25 +79,34 @@ impl fmt::Display for EndorInteger { #[derive(Debug, Clone, PartialEq, Eq)] pub struct EndorDecimal { + /// 已验证但未规范化的原十进制字面量。 literal: String, + /// 缓存尾数是否全零。 zero: bool, } impl EndorDecimal { /// Retains all digits, trailing zeroes, sign, and an arbitrarily long /// decimal exponent. No intermediate binary floating-point conversion. + /// 编译器/宿主解析字面量,构造可保留长数字,实际算术再执行预算检查。 pub fn parse(text: &str) -> Result { + // 校验时去符号,但最终仍保存完整 text,不丢失显式正号和负零。 let (_, unsigned) = split_sign(text); + // 指数最多一段,可带符号;解析不立刻收窄指数到机器整数。 let mut parts = unsigned.split(['e', 'E']); + // 尾数后续允许一个点和至少一个数字。 let mantissa = parts.next().unwrap_or_default(); + // 指数必须有数字且不得有第二个指数段。 if let Some(exponent) = parts.next() && (!is_digits(split_sign(exponent).1) || parts.next().is_some()) { return Err(VmError::InvalidArgument("invalid decimal exponent")); } + // 分别记录已有小数点、数字数量与尾数是否全零。 let mut decimal_point = false; let mut digits = 0_usize; let mut zero = true; + // 按 ASCII 字节扫描,第二个点或其他字符都拒绝。 for byte in mantissa.bytes() { match byte { b'.' if !decimal_point => decimal_point = true, @@ -93,6 +117,7 @@ impl EndorDecimal { _ => return Err(VmError::InvalidArgument("invalid decimal")), } } + // 允许 .5 和 5.,但不允许只有点或空尾数。 if digits == 0 { return Err(VmError::InvalidArgument("decimal needs digits")); } @@ -102,16 +127,19 @@ impl EndorDecimal { }) } + /// 显示和精确转换借用原字面量,保留符号、指数及尾零。 pub fn literal(&self) -> &str { &self.literal } + /// 数值真值/分母检查使用的零判断,具体实现按表示处理。 pub fn is_zero(&self) -> bool { self.zero } } impl fmt::Display for EndorDecimal { + /// Display/宿主诊断按当前表示输出,精确表示不经二进制浮点中转。 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.literal) } @@ -119,21 +147,27 @@ impl fmt::Display for EndorDecimal { #[derive(Debug, Clone, PartialEq, Eq)] pub struct EndorRational { + /// 带符号分子,不要求已约分。 numerator: EndorInteger, + /// 构造保证正且非零的分母。 denominator: EndorInteger, } impl EndorRational { /// Normalizes denominator sign, without reducing or rounding the fraction. + /// 宿主/Store 构造当前数值表示或载荷,具体不变量见下方构造步骤。 pub fn new( mut numerator: EndorInteger, mut denominator: EndorInteger, ) -> Result { + // 构造时拒绝零分母,后续零判断只需看分子。 if denominator.is_zero() { return Err(VmError::InvalidArgument("zero rational denominator")); } + // 分母归正,同时翻分子符号;不执行约分。 if denominator.negative { denominator.negative = false; + // 零分子保持正零,不引入负零。 if !numerator.is_zero() { numerator.negative = !numerator.negative; } @@ -144,16 +178,19 @@ impl EndorRational { }) } + /// 显示与精确转换借用分子表示。 pub fn numerator(&self) -> &EndorInteger { &self.numerator } + /// 显示与精确转换借用正且非零的分母表示。 pub fn denominator(&self) -> &EndorInteger { &self.denominator } } impl fmt::Display for EndorRational { + /// Display/宿主诊断按当前表示输出,精确表示不经二进制浮点中转。 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}/{}", self.numerator, self.denominator) } @@ -170,6 +207,7 @@ pub enum EndorReal { } impl EndorReal { + /// 数值真值/分母检查使用的零判断,具体实现按表示处理。 pub fn is_zero(&self) -> bool { match self { Self::Integer(value) => value.is_zero(), @@ -181,6 +219,7 @@ impl EndorReal { } impl fmt::Display for EndorReal { + /// Display/宿主诊断按当前表示输出,精确表示不经二进制浮点中转。 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Integer(value) => value.fmt(f), @@ -198,20 +237,25 @@ impl fmt::Display for EndorReal { pub enum EndorNumber { Real(EndorReal), Complex { + /// 复数实部分量。 real: EndorReal, + /// 复数虚部分量。 imaginary: EndorReal, }, } impl EndorNumber { + /// 编译器/宿主构造整数实数表示,解析错误原样传播。 pub fn integer(text: &str) -> Result { Ok(Self::Real(EndorReal::Integer(EndorInteger::parse(text)?))) } + /// 编译器/宿主构造保留字面量的十进制实数。 pub fn decimal(text: &str) -> Result { Ok(Self::Real(EndorReal::Decimal(EndorDecimal::parse(text)?))) } + /// 宿主/运算结果从两段整数文本建立有理数,拒绝零分母。 pub fn rational(numerator: &str, denominator: &str) -> Result { Ok(Self::Real(EndorReal::Rational(EndorRational::new( EndorInteger::parse(numerator)?, @@ -219,14 +263,17 @@ impl EndorNumber { )?))) } + /// 宿主显式选择二进制浮点,保存全部 IEEE 位。 pub fn binary_float(value: f64) -> Self { Self::Real(EndorReal::BinaryFloat(value.to_bits())) } + /// 组合两分量,不因虚部为零自动退化为实数;当前不支持复数算术。 pub fn complex(real: EndorReal, imaginary: EndorReal) -> Self { Self::Complex { real, imaginary } } + /// VM 算术/转换查询实数,Complex 即使虚部为零也返回 None。 pub fn as_real(&self) -> Option<&EndorReal> { match self { Self::Real(value) => Some(value), @@ -234,6 +281,7 @@ impl EndorNumber { } } + /// VM 快路径仅接受整数变体,不自动转小数或可整除分数。 pub fn as_i64(&self) -> Option { match self { Self::Real(EndorReal::Integer(value)) => value.to_i64(), @@ -241,6 +289,7 @@ impl EndorNumber { } } + /// 数值真值/分母检查使用的零判断,具体实现按表示处理。 pub fn is_zero(&self) -> bool { match self { Self::Real(value) => value.is_zero(), @@ -250,18 +299,21 @@ impl EndorNumber { } impl From for EndorNumber { + /// Rust 宿主转换入口,按输入类型选择整数文本或显式二进制浮点表示。 fn from(value: i64) -> Self { Self::integer(&value.to_string()).expect("i64 always has valid decimal digits") } } impl From for EndorNumber { + /// Rust 宿主转换入口,按输入类型选择整数文本或显式二进制浮点表示。 fn from(value: f64) -> Self { Self::binary_float(value) } } impl fmt::Display for EndorNumber { + /// Display/宿主诊断按当前表示输出,精确表示不经二进制浮点中转。 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Real(value) => value.fmt(f), @@ -272,45 +324,57 @@ impl fmt::Display for EndorNumber { #[derive(Debug, Clone)] pub struct EndorNumberObject { + /// 拥有数值表示,不含托管对象强边。 value: EndorNumber, } impl EndorNumberObject { + /// 宿主/Store 构造当前数值表示或载荷,具体不变量见下方构造步骤。 pub fn new(value: EndorNumber) -> Self { Self { value } } + /// VM/宿主借用堆数值,不增加根或复制内容。 pub fn value(&self) -> &EndorNumber { &self.value } } impl ObjectPayload for EndorNumberObject { + /// 数值载荷不可变,不实现旧式原地赋值,允许只读局部重绑定。 fn reference_assignment_only(&self) -> bool { true } + /// Store 的 Rust 载荷下转型入口。 fn as_any(&self) -> &dyn Any { self } + /// Store 错误与诊断使用的载荷类别,不是名义类型。 fn kind_name(&self) -> &'static str { "number" } + /// Store 显示数值,不打印对象地址。 fn repr(&self, _identity: ObjectRef) -> Result { Ok(self.value.to_string()) } + /// Store 真值协议按非零判断,复数要求两分量都为零才为假。 fn is_true(&self) -> Result { Ok(!self.value.is_zero()) } + /// 统一数值哈希尚未定义,拒绝以免数学相等而表示不同的键哈希不兼容。 fn hash(&self, _identity: ObjectRef) -> Result { Err(VmError::UnsupportedOperation("numeric hash")) } + /// Store 对象比较协议,只在对方也是数值载荷时处理,否则返回 None。 fn compare(&self, other: &dyn ObjectPayload, op: CompareOp) -> Result, VmError> { + // 异类载荷不强行比较,交给 Store 的反向协议或身份回退。 if let Some(other) = other.as_any().downcast_ref::() { + // 复数目前连相等比较也不支持,明确报协议错误。 if self.value.as_real().is_none() || other.value.as_real().is_none() { return Err(VmError::UnsupportedOperation("numeric comparison")); } @@ -320,7 +384,9 @@ impl ObjectPayload for EndorNumberObject { } } +/// 解析共用单个符号拆分,不去空白或重复符号。 fn split_sign(text: &str) -> (bool, &str) { + // 只剥离一个前导符号,剩余文本交给数字语法校验。 if let Some(rest) = text.strip_prefix('-') { (true, rest) } else { @@ -328,6 +394,7 @@ fn split_sign(text: &str) -> (bool, &str) { } } +/// 解析共用非空 ASCII 数字检查,不接受 Unicode 数字。 fn is_digits(text: &str) -> bool { !text.is_empty() && text.bytes().all(|byte| byte.is_ascii_digit()) } @@ -343,6 +410,7 @@ pub enum ArithmeticOp { Divide, } impl ArithmeticOp { + /// eval 解 BinaryArithmetic 的 0..3 运算编号,其他值返回 None。 pub fn from_argument(value: u32) -> Option { match value { 0 => Some(Self::Add), @@ -354,7 +422,9 @@ impl ArithmeticOp { } } impl EndorNumber { + /// eval 一元负号:浮点翻符号位,其余走零减当前值,因此仍受运算预算限制。 pub fn negated(&self) -> Result { + // 翻转符号位保留负零和 NaN payload,不经算术规范化。 if let Some(EndorReal::BinaryFloat(bits)) = self.as_real() { return Ok(Self::Real(EndorReal::BinaryFloat(bits ^ (1_u64 << 63)))); } @@ -362,11 +432,14 @@ impl EndorNumber { } /// Explicit narrowing for graphics/FFI boundaries; exact values themselves /// retain their representation. Non-real or non-finite values are rejected. + /// 宿主图形/FFI 显式收窄,拒绝复数、非有限值和不可转换范围。 pub fn to_f64(&self) -> Result { use num_traits::ToPrimitive; + // 先要求 Real,不能静默丢弃复数虚部。 let real = self .as_real() .ok_or(VmError::UnsupportedOperation("complex to float"))?; + // 浮点还原原位,精确表示先转 BigRational 再收窄。 let value = if let EndorReal::BinaryFloat(bits) = real { f64::from_bits(*bits) } else { @@ -374,13 +447,17 @@ impl EndorNumber { "number exceeds binary float range", ))? }; + // 图形/FFI 边界不接受 NaN 或无穷。 if !value.is_finite() { return Err(VmError::InvalidArgument("non-finite float conversion")); } Ok(value) } + /// eval 二元算术:i64 安全快路径,任一浮点选择 float,否则精确有理数运算。 pub fn arithmetic(&self, other: &Self, op: ArithmeticOp) -> Result { + // 双方为可容纳 i64 的 Integer 才用快路径;运算溢出回退,不截断。 if let (Some(a), Some(b)) = (self.as_i64(), other.as_i64()) { + // 加减乘检查溢出,除法仅整除且不溢出时保持整数快路径。 let integer = match op { ArithmeticOp::Add => a.checked_add(b), ArithmeticOp::Subtract => a.checked_sub(b), @@ -390,11 +467,13 @@ impl EndorNumber { .filter(|&r| r == 0) .and_then(|_| a.checked_div(b)), }; + // 快路径成功后包装为规范化整数。 if let Some(result) = integer { return Ok(Self::from(result)); } } use num_traits::{ToPrimitive, Zero}; + // 先要求两个输入均为实数;后续按精确/浮点模式转换。 let (a, b) = ( self.as_real() .ok_or(VmError::UnsupportedOperation("complex arithmetic"))?, @@ -402,8 +481,11 @@ impl EndorNumber { .as_real() .ok_or(VmError::UnsupportedOperation("complex arithmetic"))?, ); + // 显式浮点参与才启用 float,精确十进制本身不自动选 float。 if matches!(a, EndorReal::BinaryFloat(_)) || matches!(b, EndorReal::BinaryFloat(_)) { + // 混合算术中精确侧可收窄,浮点侧还原原位。 let convert = |n: &EndorReal| -> Result { + // 已为 float 则还原,否则精确转换后检查能否收窄。 if let EndorReal::BinaryFloat(bits) = n { Ok(f64::from_bits(*bits)) } else { @@ -412,44 +494,55 @@ impl EndorNumber { )) } }; + // 先要求两个输入均为实数;后续按精确/浮点模式转换。 let (a, b) = (convert(a)?, convert(b)?); + // 两种运算模式都拒绝零除,包括浮点正负零。 if matches!(op, ArithmeticOp::Divide) && b == 0. { return Err(VmError::InvalidArgument("division by zero")); } + // 执行指定运算;浮点后检查有限性,精确后检查结果预算。 let result = match op { ArithmeticOp::Add => a + b, ArithmeticOp::Subtract => a - b, ArithmeticOp::Multiply => a * b, ArithmeticOp::Divide => a / b, }; + // 拒绝 NaN 传播或溢出为无穷,不返回非有限算术结果。 if !result.is_finite() { return Err(VmError::InvalidArgument("non-finite arithmetic result")); } return Ok(Self::binary_float(result)); } + // 先要求两个输入均为实数;后续按精确/浮点模式转换。 let (a, b) = (exact_real(a)?, exact_real(b)?); + // 两种运算模式都拒绝零除,包括浮点正负零。 if matches!(op, ArithmeticOp::Divide) && b.is_zero() { return Err(VmError::InvalidArgument("division by zero")); } + // 执行指定运算;浮点后检查有限性,精确后检查结果预算。 let result = match op { ArithmeticOp::Add => a + b, ArithmeticOp::Subtract => a - b, ArithmeticOp::Multiply => a * b, ArithmeticOp::Divide => a / b, }; + // 限制运算后分子/分母位数,构造保存上限与实际运算预算不同。 if result.numer().bits() > 16_384 || result.denom().bits() > 16_384 { return Err(VmError::InvalidArgument( "arithmetic result exceeds operation budget", )); } + // 可整除输出 Integer,否则输出约分后 Rational,不伪造 Decimal 小数位。 if result.is_integer() { Self::integer(&result.to_integer().to_string()) } else { Self::rational(&result.numer().to_string(), &result.denom().to_string()) } } + /// eval/数值载荷数学比较,拒绝复数,处理 NaN 无序与无穷,不依赖 Rust Eq。 pub fn numeric_compare(&self, other: &Self, op: CompareOp) -> Result { use std::cmp::Ordering; + // 先要求两个输入均为实数;后续按精确/浮点模式转换。 let (a, b) = ( self.as_real() .ok_or(VmError::UnsupportedOperation("complex comparison"))?, @@ -457,6 +550,7 @@ impl EndorNumber { .as_real() .ok_or(VmError::UnsupportedOperation("complex comparison"))?, ); + // 双方均为可容纳 i64 的 Integer 时直接比较,不需要大整数分配。 if let (Some(a), Some(b)) = (self.as_i64(), other.as_i64()) { return Ok(match op { CompareOp::Eq => a == b, @@ -469,15 +563,18 @@ impl EndorNumber { } // Finite binary floats compare as exact binary rationals, so a large // integer never loses precision merely to answer an ordering question. + // 双浮点使用 partial_cmp;有限混合比较转精确分数,避免大整数失真。 let order = if let (EndorReal::BinaryFloat(a), EndorReal::BinaryFloat(b)) = (a, b) { f64::from_bits(*a).partial_cmp(&f64::from_bits(*b)) } else { + // 识别混合比较中的浮点 NaN/无穷,其他表示只有有限值。 let nonfinite = |n: &EndorReal| match n { EndorReal::BinaryFloat(bits) if !f64::from_bits(*bits).is_finite() => { Some(f64::from_bits(*bits)) } _ => None, }; + // NaN 无序,无穷按符号超出有限值,其他输入走精确比较。 match (nonfinite(a), nonfinite(b)) { (Some(a), _) if a.is_nan() => None, (_, Some(b)) if b.is_nan() => None, @@ -494,6 +591,7 @@ impl EndorNumber { _ => Some(exact_real(a)?.cmp(&exact_real(b)?)), } }; + // 无序令 Eq 和有序关系为 false,Ne 为 true。 Ok(match op { CompareOp::Eq => order == Some(Ordering::Equal), CompareOp::Ne => order != Some(Ordering::Equal), @@ -504,10 +602,13 @@ impl EndorNumber { }) } } +/// 算术/比较/收窄共用精确有理数转换,有限浮点按精确二进制分数处理,有操作预算。 fn exact_real(value: &EndorReal) -> Result { use num_bigint::BigInt; use num_rational::BigRational; + // 大整数转换先限制文本长度,含可能的符号。 let integer = |s: &str| -> Result { + // 超预算报错,不以截断或浮点近似继续。 if s.len() > 4096 { return Err(VmError::InvalidArgument( "numeric operand exceeds operation budget", @@ -525,7 +626,9 @@ fn exact_real(value: &EndorReal) -> Result { EndorReal::BinaryFloat(bits) => BigRational::from_float(f64::from_bits(*bits)) .ok_or(VmError::InvalidArgument("non-finite exact conversion")), EndorReal::Decimal(n) => { + // 十进制已通过构造语法检查,这里为运算解析指数。 let parts: Vec<_> = n.literal.split(['e', 'E']).collect(); + // 保存允许很长指数,实际运算要求可转为 i32。 let exponent = if parts.len() == 2 { parts[1].parse::().map_err(|_| { VmError::InvalidArgument("numeric exponent exceeds operation budget") @@ -533,15 +636,21 @@ fn exact_real(value: &EndorReal) -> Result { } else { 0 }; + // 尾数的小数位数须从指数扣除,确定整体十进制缩放。 let decimals = parts[0].split_once('.').map_or(0, |(_, s)| s.len()) as i64; + // 提升到 i64 计算指数差,随后检查绝对幂次预算。 let power = i64::from(exponent) - decimals; + // 限制生成巨大十次幂的工作量,不只限制结果显示长度。 if power.abs() > 4096 { return Err(VmError::InvalidArgument( "numeric exponent exceeds operation budget", )); } + // 去掉小数点但保留符号,将尾数转为整数。 let digits = integer(&parts[0].replace('.', ""))?; + // 预算通过才计算精确的 10 次幂,不调用浮点 pow。 let factor = BigInt::from(10).pow(power.unsigned_abs() as u32); + // 负幂放分母,非负幂乘分子;BigRational 规范化结果。 Ok(if power < 0 { BigRational::new(digits, factor) } else { -- 2.52.0 From 58ec874dc0d82bfebb22417f59cf747d9a6e66be Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:33:03 +0800 Subject: [PATCH 10/62] docs(objects): explain snapshot call effect analysis --- crates/objects/src/effects.rs | 65 +++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/crates/objects/src/effects.rs b/crates/objects/src/effects.rs index cdd89e9..2dafb7a 100644 --- a/crates/objects/src/effects.rs +++ b/crates/objects/src/effects.rs @@ -1,3 +1,5 @@ +//! Store::call_effect 的当前快照证明;None 表示无法证明,不代表确定有副作用。 +//! 查询经过 Store 读取追踪,但不执行源码或分配托管对象;所有身份仅借用。 //! Snapshot proof of read-only call graphs. All identities are borrowed and all //! traversals iterative; analysis neither executes code nor allocates VM objects. use super::{AttributeLookup, BoundMethod}; @@ -14,18 +16,24 @@ struct Target { } #[derive(Clone, Copy, PartialEq, Eq)] enum Fact { + // 没有可证明的身份,不是实际 Nil。 Unknown, + // 调用协议 Null 标记,不是语言空值。 Null, + // 已知借用对象身份,不增加根引用。 Object(ObjectRef), + // 已解析函数和绑定接收者,不创建托管绑定对象。 Method(Target), } impl Fact { + // 把实际值降为抽象身份事实,直接标量不保留其内容。 fn value(value: Value) -> Self { match value { Value::Object(object) => Self::Object(object), _ => Self::Unknown, } } + // 只从普通对象事实提取身份,不把方法对当作对象。 fn object(self) -> Option { match self { Self::Object(object) => Some(object), @@ -35,9 +43,13 @@ impl Fact { } impl ObjectStore { + // 解包 BoundMethod,保留底层函数和实际接收者,供调用图节点归一化。 fn effect_target(&self, mut target: Target) -> Result { + // 逐层解绑定载荷,不递归进入 Rust 调用栈。 loop { + // 验证函数身份并记录当前载荷读取,避免绕过依赖追踪。 let payload = self.object(target.function)?.payload(); + // 不是绑定方法即结束归一化,不更改现有接收者。 let Some(bound) = payload.as_any().downcast_ref::() else { return Ok(target); }; @@ -48,14 +60,17 @@ impl ObjectStore { } } + // types::call_effect 委托入口;原生根返回已声明效果,源码根只尝试证明全图只读。 pub(super) fn infer_call_effect( &self, function: ObjectRef, ) -> Result, VmError> { + // 根也可能是绑定方法,先解包再识别源码/原生。 let root = self.effect_target(Target { function, receiver: None, })?; + // 非源码根查询原生签名,Delegated 效果动态决定,不能直接当作整体效果。 if !self .object(root.function)? .payload() @@ -66,19 +81,26 @@ impl ObjectStore { (signature.effect != CallEffect::Delegated).then_some(signature.effect) })); } + // 迭代工作队列防止递归调用图造成 Rust 递归。 let mut pending = VecDeque::from([root]); + // 节点去重包含接收者,同一函数的不同绑定对象不能混为一项。 let mut visited = BTreeSet::new(); + // 所有可达节点都能证明只读才允许根返回 ReadOnly。 while let Some(target) = pending.pop_front() { + // 子调用也可能经过绑定方法,统一归一化。 let target = self.effect_target(target)?; + // 递归回边已访问就跳过,不重复展开。 if !visited.insert(target) { continue; } + // 源码节点分析栈事实,原生节点则必须明确声明 ReadOnly。 if let Some(source) = self .object(target.function)? .payload() .as_any() .downcast_ref::() { + // 任意一个节点无法证明,整个源码根的证明立即返回 None。 let Some(callees) = self.read_only_callees(source, target.receiver)? else { return Ok(None); }; @@ -87,41 +109,55 @@ impl ObjectStore { .native_signature(target.function)? .is_none_or(|signature| signature.effect != CallEffect::ReadOnly) { - // An impure target could rebind later globals/Cells/methods, so - // snapshot edges cannot bound this source invocation's effects. + // 未知/非只读目标可重绑定后续全局、Cell 或方法,当前快照不足以证明整体效果。 return Ok(None); } } Ok(Some(CallEffect::ReadOnly)) } + // 分析一个源码函数的抽象栈,收集已知被调目标;不执行实际调用。 fn read_only_callees( &self, function: &EndorFunctionObject, receiver: Option, ) -> Result>, VmError> { + // 借用函数持有的代码;后续索引和栈高度依赖代码构造时的验证。 let code = self .payload::(function.code(), "code")? .code(); + // 全表白名单已足够时无需分析调用图,返回空被调集合。 if code.is_statically_read_only() { return Ok(Some(BTreeSet::new())); } + // 有 Call 时要求其他全部指令都通过本地白名单。 let instructions = code.instructions(); + // 过滤覆盖不可达指令,不仅检查当前可达路径。 if instructions.iter().any(|decoded| { decoded.instruction.opcode != Opcode::Call && !crate::code::read_only_opcode(decoded.instruction.opcode) }) { return Ok(None); } + // 每个逻辑指令入口一份抽象栈,None 表示尚未到达。 let mut states: Vec>> = vec![None; instructions.len()]; + // 已验证代码非空,入口求值栈为空。 states[0] = Some(vec![]); + // 分支汇合只能遗忘事实,状态变保守后再入队,最终收敛。 let mut work = VecDeque::from([0]); + // 调用边只增加不删除,保守保留曾经识别的目标。 let mut callees = BTreeSet::new(); + // 逐指令模拟身份流动,不计算实际数值或条件。 while let Some(index) = work.pop_front() { + // 偏移使用物理代码单元,工作队列使用逻辑指令索引。 let decoded = instructions[index]; + // 操作码控制抽象栈变换,不在分析阶段执行其载荷协议。 let opcode = decoded.instruction.opcode; + // 完整操作数已合并扩展前缀。 let argument = decoded.instruction.argument as usize; + // 复制当前入口事实,模拟本步后再传给后继。 let mut stack = states[index].clone().expect("reachable effect state"); + // 仅保留调用目标识别所需事实,结果通常退化为 Unknown。 match opcode { Opcode::Await => return Ok(None), Opcode::LoadConst => stack.push(Fact::value(code.constants()[argument].clone())), @@ -131,8 +167,11 @@ impl ObjectStore { Fact::Unknown }), Opcode::LoadGlobal => { + // 与 eval 一致,先查询全局字典,再回退捕获的内置空间。 let name = &code.layout().names[argument]; + // 名称读取登记命中/缺失依赖,后续重绑定能影响快照证明。 let mut value = self.dict_get_name(function.globals(), name)?; + // 只有缺键才回退,Some(Nil) 不等于名称不存在。 if value.is_none() && let Some(builtins) = function.builtins() { @@ -141,16 +180,20 @@ impl ObjectStore { stack.push(value.map(Fact::value).unwrap_or(Fact::Unknown)); } Opcode::LoadDeref | Opcode::LoadClosure => { + // 外部自由变量可读当前捕获 Cell;局部只尝试识别已知接收者。 let fact = if argument >= code.layout().locals.len() { + // 把 localsplus 自由变量段索引换算到函数 closure 列表。 let cell = *function .closure() .get(argument - code.layout().locals.len()) .ok_or(VmError::InvalidArgument( "function closure does not match code", ))?; + // LoadClosure 保留 Cell 自身身份,LoadDeref 读取其当前内容。 if opcode == Opcode::LoadClosure { Fact::Object(cell) } else { + // 空 Cell 为 Unknown,其他协议/句柄错误仍传播。 match self.cell_get(cell) { Ok(value) => Fact::value(value), Err(VmError::EmptyCell) => Fact::Unknown, @@ -167,6 +210,7 @@ impl ObjectStore { Opcode::PushNull => stack.push(Fact::Null), Opcode::Copy => stack.push(stack[stack.len() - argument]), Opcode::Swap => { + // Swap 深度已由代码验证,直接模拟事实位置交换。 let length = stack.len(); stack.swap(length - 1, length - argument); } @@ -184,8 +228,11 @@ impl ObjectStore { *stack.last_mut().unwrap() = Fact::Unknown; } Opcode::LoadAttr => { + // 只有 owner 身份已知才可查询其属性,未知 owner 不猜测方法。 let fact = stack.last_mut().unwrap(); + // 绑定方法保留函数与接收者对,不实际分配方法载荷。 *fact = if let Some(owner) = fact.object() { + // 缺属性/不支持属性视为未知,其他错误不吞掉。 match self.lookup_attr(owner, &code.layout().names[argument]) { Ok(AttributeLookup::Value(value)) => Fact::value(value), Ok(AttributeLookup::Method { function, receiver }) => { @@ -205,7 +252,9 @@ impl ObjectStore { }; } Opcode::Call => { + // 调用输入包括函数、接收者或 Null 及显式参数,代码校验保证足够高度。 let start = stack.len() - argument - 2; + // Null 协议从首显式实参识别可能接收者,否则用隐式接收者项。 let implicit = if stack[start + 1] == Fact::Null { (argument > 0) .then(|| stack[start + 2]) @@ -213,6 +262,7 @@ impl ObjectStore { } else { stack[start + 1].object() }; + // 未知函数目标无法证明整个函数;已知方法直接保留绑定接收者。 let target = match stack[start] { Fact::Object(function) => Target { function, @@ -221,6 +271,7 @@ impl ObjectStore { Fact::Method(target) => target, _ => return Ok(None), }; + // 记录目标后移除输入,调用结果不猜测身份,标为 Unknown。 callees.insert(target); stack.truncate(start); stack.push(Fact::Unknown); @@ -230,14 +281,17 @@ impl ObjectStore { stack.push(Fact::Unknown); } Opcode::MakeFunction => { + // 函数构造消耗默认值、捕获槽和代码项;新对象未实际分配,结果未知。 let (defaults, closure) = crate::Instruction::function_counts(decoded.instruction.argument); stack.truncate(stack.len() - defaults - closure - 1); stack.push(Fact::Unknown); } Opcode::Nop | Opcode::DeleteFast | Opcode::JumpForward | Opcode::JumpBackward => {} + // 注意:当前白名单含算术操作,但此模拟器尚无对应分支;含 Call 的混合路径需单独修复。 _ => unreachable!("unproved opcodes were rejected before analysis"), } + // 条件跳转传播两条后继,返回无后继,不根据当前值猜测条件真假。 let successors = match opcode { Opcode::ReturnValue => vec![], Opcode::JumpForward | Opcode::JumpBackward => { @@ -248,13 +302,19 @@ impl ObjectStore { } _ => vec![decoded.next_offset], }; + // 后继都已通过代码验证,必须落在完整逻辑指令起点。 for offset in successors { + // 将物理单元偏移查为逻辑索引,expect 依赖已验证控制流。 let next = instructions .binary_search_by_key(&offset, |item| item.offset) .expect("validated successor"); + // 汇合时逐槽遗忘有分歧的身份;栈长度由代码验证保证一致。 let changed = if let Some(existing) = &mut states[next] { + // 只有新增状态或事实首次退化才再次传播。 let mut changed = false; + // Unknown 不恢复为具体事实,相同身份继续保留。 for (old, new) in existing.iter_mut().zip(&stack) { + // 不同路径的目标不能任取其一,合并为 Unknown。 if *old != *new && *old != Fact::Unknown { *old = Fact::Unknown; changed = true; @@ -265,6 +325,7 @@ impl ObjectStore { states[next] = Some(stack.clone()); true }; + // 状态更保守时重新处理后继,直至所有入口稳定。 if changed { work.push_back(next); } -- 2.52.0 From c05d60cbc9431aa8af83278f95f47b1bf9a00311 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:35:00 +0800 Subject: [PATCH 11/62] docs(objects): explain method substitution contracts --- crates/objects/src/contracts.rs | 114 ++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/crates/objects/src/contracts.rs b/crates/objects/src/contracts.rs index f44f491..1f91292 100644 --- a/crates/objects/src/contracts.rs +++ b/crates/objects/src/contracts.rs @@ -1,3 +1,6 @@ +//! Store 类型/能力方法的替换约束;types 子模块可读取方法表,无需向宿主公开可变内部布局。 +//! 参数要求逆变、返回要求协变;检查只观察当前定义,拟议更新通过 Patch 预演,不先修改再回滚。 +//! UnknownAnnotation 仅在显式 defer_unknown 时暂缓,调用时检查使用完整解析路径。 //! Method substitution rules. This is a child of types so validation can inspect //! method tables without exporting mutable definition internals. use super::{AnnotationType, EndorTypeObject}; @@ -7,18 +10,25 @@ use crate::{ }; struct Contract { + /// 可接受的位置参数区间,包括接收者。 arity: CallArity, + /// 固定位置参数约束。 parameters: Vec, + /// 固定部分之后的统一参数约束。 variadic: Option, + /// 实现返回值必须落入父契约或能力允许范围。 returns: CallType, + /// 已知整体效果,None 表示不能证明,不等于 ReadOnly。 effect: Option, } impl Contract { + /// 签名替换检查按位置选固定参数,超出固定部分回退可变参数约束。 fn parameter(&self, index: usize) -> Option<&CallType> { self.parameters.get(index).or(self.variadic.as_ref()) } } impl From for Contract { + /// 原生签名转内部契约,Delegated 不是已知整次调用效果,转换为 None。 fn from(value: CallSignature) -> Self { Self { arity: value.arity, @@ -35,25 +45,35 @@ impl From for Contract { } #[derive(Clone, Copy)] struct Patch<'a> { + /// 拟议更新所属的类型身份,借用。 owner: ObjectRef, + /// 拟议更新的方法名,仅精确匹配此名。 name: &'a str, + /// 拟议的新函数身份,不由 Patch 保留根。 function: ObjectRef, } struct TraitCheckGuard<'a> { + /// 借用递归校验状态表。 store: &'a ObjectStore, + /// 创建守卫时的线程身份。 thread: std::thread::ThreadId, + /// 当前定义/能力对,Drop 时移除。 key: (ObjectRef, ObjectRef), } impl Drop for TraitCheckGuard<'_> { + /// 递归校验 RAII 清理,正常/报错/展开路径都移除本线程当前定义与能力对。 fn drop(&mut self) { + // 取得线程校验表的锁,守卫只会在成功插入 key 后创建。 let mut checks = self .store .active_trait_checks .lock() .expect("trait checker poisoned"); + // 只删除当前递归对,不影响同线程其他活跃校验。 let active = checks.get_mut(&self.thread).expect("active trait check"); active.remove(&self.key); + // 线程没有剩余递归检查时移除空项。 if active.is_empty() { checks.remove(&self.thread); } @@ -64,12 +84,18 @@ impl CallType { /// Whether every value admitted by provided is admitted by self. /// Nominal inheritance and explicit capabilities participate; no structural /// or callable-signature inference is implied by this relation. + /// 替换校验的值集合包含关系:provided 的每项都必须被 self 的至少一项接纳。 pub fn accepts_type(&self, store: &ObjectStore, provided: &Self) -> Result { + // 先验证联合全部定义边,再做包含/匹配判断,不能让无效分支藏在后面。 self.validate(store)?; provided.validate(store)?; + // provided 是待被接纳的值集合,必须逐个候选分支都被覆盖。 for actual in provided.alternatives() { + // 每个 provided 分支单独寻找一个可接受它的 self 分支。 let mut accepted = false; + // self 联合只需一个分支能接纳当前 actual,不要求所有分支都匹配。 for expected in self.alternatives() { + // Any 接受所有值;名义继承和显式能力参与,不推断结构相似或可调用签名。 accepted = match (*expected, *actual) { (AnnotationType::Any, _) => true, (AnnotationType::Nominal(base), AnnotationType::Nominal(child)) => { @@ -81,10 +107,12 @@ impl CallType { ) => store.implements_trait(definition, capability)?, (expected, actual) => expected == actual, }; + // 当前 actual 已找到覆盖分支即可结束内层循环。 if accepted { break; } } + // 存在一个未被接纳的 provided 分支即整体不包含。 if !accepted { return Ok(false); } @@ -94,14 +122,18 @@ impl CallType { } impl ObjectStore { + /// types::validate_trait 调用的递归守卫;同线程同定义/能力对重入时暂不重复展开。 pub(super) fn with_trait_check( &self, definition: ObjectRef, capability: ObjectRef, check: impl FnOnce() -> Result<(), VmError>, ) -> Result<(), VmError> { + // 递归重入按线程隔离,避免把其他线程的检查误作当前递归。 let thread = std::thread::current().id(); + // 重入身份包含类型定义与能力,不仅是单个类型。 let key = (definition, capability); + // 同线程同定义/能力对已经在检查时返回,避免递归契约无限展开。 if !self .active_trait_checks .lock() @@ -112,6 +144,7 @@ impl ObjectStore { { return Ok(()); } + // 锁已释放,守卫负责退出时清除 key;执行 check 时不持全局校验锁。 let _guard = TraitCheckGuard { store: self, thread, @@ -119,14 +152,17 @@ impl ObjectStore { }; check() } + /// 替换检查读取原生签名或解析源码签名,缺失元数据返回 None,不猜测类型。 fn method_contract( &self, function: ObjectRef, owner: ObjectRef, ) -> Result, VmError> { + // 优先使用已解析原生签名,其引用有效性由 Store 的签名入口校验。 if let Some(native) = self.native_signature(function)? { return Ok(Some(native.into())); } + // 既非原生完整签名又非源码函数时无法取得契约。 let Some(data) = self .object(function)? .payload() @@ -135,15 +171,23 @@ impl ObjectStore { else { return Ok(None); }; + // 源码类型声明在函数指向的代码对象内,不从函数名字猜测。 let code = self.payload::(data.code(), "code")?.code(); + // 源码没有声明就返回 None,调用方决定缺失元数据是否允许。 let Some(signature) = code.source_signature() else { return Ok(None); }; + // 解析源码 owner 名称时考虑继承中的真实声明,避免只查全局同名对象。 let resolve = |name: &str| { + // 仅源码声明的 owner 名称走特殊继承解析,其余名称回退常规注解解析。 if signature.method_owner.as_deref() == Some(name) { + // 从当前声明 owner 向基类查找,不扫描任意后代。 let mut current = Some(owner); + // 逐层读取不可变类型定义,必要时确认父层方法身份仍是当前函数。 while let Some(definition) = current { + // 读取类型载荷并验证句柄,后续方法表与基类访问都基于该借用。 let data = self.payload::(definition, "Type")?; + // 名称相同还需属于当前 owner 或确实在父方法表持有该函数。 if data.name == name && (definition == owner || data @@ -158,6 +202,7 @@ impl ObjectStore { } self.annotation_type(function, name, None) }; + // 逐个解析全部参数注解,首个错误即传播,不返回部分契约。 let parameters = signature .parameters .iter() @@ -173,12 +218,14 @@ impl ObjectStore { effect: self.call_effect(function)?, })) } + /// 查类型自身方法,若 Patch 精确匹配该类型/名称则读取拟议替代函数。 fn own_method( &self, owner: ObjectRef, name: &str, patch: Option>, ) -> Result, VmError> { + // 只在 owner 和 name 都相同的查询位置替换视图,不修改真实方法表。 if let Some(patch) = patch && patch.owner == owner && patch.name == name @@ -191,13 +238,16 @@ impl ObjectStore { .get(name) .copied()) } + /// 沿单继承链寻找最近方法及声明 owner,支持拟议 Patch 的查找视图。 fn inherited_method( &self, mut owner: Option, name: &str, patch: Option>, ) -> Result, VmError> { + // 向基类遍历;不假定候选函数一定是接收者当前生效的方法。 while let Some(definition) = owner { + // 最靠近当前类型的声明遮蔽更远的同名父方法。 if let Some(function) = self.own_method(definition, name, patch)? { return Ok(Some((definition, function))); } @@ -205,6 +255,7 @@ impl ObjectStore { } Ok(None) } + /// 检查当前方法替换最近父方法是否可代换;父方法无类型元数据时保持兼容。 fn check_override( &self, owner: ObjectRef, @@ -213,32 +264,40 @@ impl ObjectStore { patch: Option>, defer_unknown: bool, ) -> Result<(), VmError> { + // 检查 owner 类型载荷,诊断使用其声明名称。 let definition = self.payload::(owner, "Type")?; + // 没有同名父方法即不是 override,无需普通替换检查。 let Some((base_owner, base_function)) = self.inherited_method(definition.base, name, patch)? else { return Ok(()); }; + // 闭包收集解析/校验结果,外层仅在允许时暂缓 UnknownAnnotation。 let checked = (|| { + // 父方法没有完整元数据时不新增更严格的旧接口约束。 let Some(base) = self.method_contract(base_function, base_owner)? else { return Ok(()); }; + // 按当前调用用途构造带类型/能力与方法定位的错误,不改变验证逻辑。 let invalid = |reason| VmError::OverrideViolation { definition: definition.name.clone(), method: name.into(), reason, }; + // 父契约存在时,派生实现也必须提供完整可检查的类型元数据。 let derived = self .method_contract(function, owner)? .ok_or_else(|| invalid("override lacks type metadata"))?; self.check_substitution(owner, &derived, &base, invalid)?; Ok(()) })(); + // 只有 UnknownAnnotation 可在 defer_unknown 时暂缓,其他失败始终传播。 match checked { Err(VmError::UnknownAnnotation(_)) if defer_unknown => Ok(()), result => result, } } + /// 父方法/能力签名的公共替换算法;所有不兼容原因交给调用方构造具体错误。 fn check_substitution( &self, owner: ObjectRef, @@ -246,12 +305,15 @@ impl ObjectStore { base: &Contract, invalid: impl Fn(&'static str) -> VmError, ) -> Result<(), VmError> { + // 实现不得要求更多必填参数,也不得缩小最多可接收参数数量。 if derived.arity.required > base.arity.required || derived.arity.maximum < base.arity.maximum { return Err(invalid("override narrows accepted argument counts")); } + // 接收者按实现声明类型核对,不能只对比两份签名的第零项文本。 let receiver = CallType::from(AnnotationType::Nominal(owner)); + // 派生签名必须接受它自己声明类型的实例作为接收者。 if !derived .parameter(0) .ok_or_else(|| invalid("override lacks receiver"))? @@ -259,6 +321,7 @@ impl ObjectStore { { return Err(invalid("override receiver rejects its declaring type")); } + // 父/能力签名必须接纳实现类型,返回值则必须落在父约束范围。 if !base .parameter(0) .ok_or_else(|| invalid("required signature lacks receiver"))? @@ -266,48 +329,62 @@ impl ObjectStore { { return Err(invalid("required receiver rejects implementing type")); } + // 检查双方固定参数覆盖到的最大位置,但不超过父方法允许上界。 let fixed = base .parameters .len() .max(derived.parameters.len()) .min(base.arity.maximum); + // 跳过已单独核对的接收者,第一个普通参数起检查逆变。 for index in 1..fixed { + // 调用方按父契约可传的类型集合,实现必须全部接纳。 let expected = base .parameter(index) .ok_or_else(|| invalid("incomplete base parameter metadata"))?; + // 实现的实际参数/方法契约必须存在,不能通过缺失元数据绕过检查。 let actual = derived .parameter(index) .ok_or_else(|| invalid("incomplete override parameter metadata"))?; + // 逆变方向是 actual 接纳 expected,反过来会错误允许缩窄参数。 if !actual.accepts_type(self, expected)? { return Err(invalid("override narrows a parameter type")); } } + // 父契约还有固定段之后的位置时,用该位置的尾部约束核对其余参数。 if base.arity.maximum > fixed { + // 调用方按父契约可传的类型集合,实现必须全部接纳。 let expected = base .parameter(fixed) .ok_or_else(|| invalid("incomplete base variadic metadata"))?; + // 实现的实际参数/方法契约必须存在,不能通过缺失元数据绕过检查。 let actual = derived .parameter(fixed) .ok_or_else(|| invalid("incomplete override variadic metadata"))?; + // 逆变方向是 actual 接纳 expected,反过来会错误允许缩窄参数。 if !actual.accepts_type(self, expected)? { return Err(invalid("override narrows a variadic parameter type")); } } + // 父/能力签名必须接纳实现类型,返回值则必须落在父约束范围。 if !base.returns.accepts_type(self, &derived.returns)? { return Err(invalid("override widens the return type")); } + // 仅双方效果都已知时比较;未知效果不在此处强行推断。 if let (Some(expected), Some(actual)) = (base.effect, derived.effect) { + // 只读只能替换为只读;Mutating 不接受 External;External/Delegated 不再收紧。 let compatible = match expected { CallEffect::ReadOnly => actual == CallEffect::ReadOnly, CallEffect::Mutating => actual != CallEffect::External, CallEffect::External | CallEffect::Delegated => true, }; + // 效果扩大报替换错误,不靠实际执行检测。 if !compatible { return Err(invalid("override expands known effects")); } } Ok(()) } + /// types 能力验证把实现方法与声明签名比较,报 TraitViolation 而非普通覆盖错误。 pub(super) fn validate_typed_requirement( &self, definition: ObjectRef, @@ -317,6 +394,7 @@ impl ObjectStore { expected: &CallSignature, defer_unknown: bool, ) -> Result<(), VmError> { + // 按当前调用用途构造带类型/能力与方法定位的错误,不改变验证逻辑。 let invalid = |reason| VmError::TraitViolation { capability: self .payload::(capability, "Trait") @@ -326,38 +404,49 @@ impl ObjectStore { method: name.into(), reason, }; + // 闭包收集解析/校验结果,外层仅在允许时暂缓 UnknownAnnotation。 let checked = (|| { + // 实现的实际参数/方法契约必须存在,不能通过缺失元数据绕过检查。 let actual = self .method_contract(function, definition)? .ok_or_else(|| invalid("required member lacks type metadata"))?; self.check_substitution(definition, &actual, &expected.clone().into(), invalid) })(); + // 只有 UnknownAnnotation 可在 defer_unknown 时暂缓,其他失败始终传播。 match checked { Err(VmError::UnknownAnnotation(_)) if defer_unknown => Ok(()), result => result, } } + /// 类型构造时按开关校验自身方法覆盖,允许暂缓尚未解析的注解。 pub(super) fn validate_class_overrides(&self, definition: ObjectRef) -> Result<(), VmError> { + // 类定义/更新阶段普通 override 检查受配置开关控制。 if self.field_type_checks { + // 逐个检查当前类型自身方法,不把继承方法当作自身声明。 for (name, function) in &self.payload::(definition, "Type")?.methods { self.check_override(definition, name, *function, None, true)?; } } Ok(()) } + /// set_method 提交前,以 Patch 校验受影响后代,避免先改表再发现错误。 pub(super) fn validate_override_update( &self, owner: ObjectRef, name: &str, function: ObjectRef, ) -> Result<(), VmError> { + // 类定义/更新阶段普通 override 检查受配置开关控制。 if self.field_type_checks { + // 拟议 owner/name/function 只作为查询视图,验证完成前不提交。 let patch = Some(Patch { owner, name, function, }); + // 父方法变化可能破坏已有子方法的替换关系,所以检查全部后代。 for descendant in self.descendant_types(owner)? { + // 最靠近当前类型的声明遮蔽更远的同名父方法。 if let Some(function) = self.own_method(descendant, name, patch)? { self.check_override(descendant, name, function, patch, true)?; } @@ -366,6 +455,7 @@ impl ObjectStore { Ok(()) } /// Explicit override validation, including any typed capabilities. + /// VM/宿主显式启用普通覆盖检查,同时校验接收者的类型化能力约束。 pub fn check_method_override_call( &self, function: ObjectRef, @@ -376,25 +466,35 @@ impl ObjectStore { } /// Typed capabilities are explicit opt-ins and always checked. Ordinary /// override checking remains configurable by the VM's source-check flag. + /// eval 原生调用前核对实际方法;能力约束始终检查,普通覆盖由 overrides 控制。 pub fn check_method_contract_call( &self, function: ObjectRef, receiver: ObjectRef, overrides: bool, ) -> Result { + // 返回值表示是否命中类型化能力约束,不代表普通覆盖检查是否执行。 let mut typed = false; + // 接收者的有效类型可能经 type_target 转发;无类型时不会枚举名义方法。 let actual = self.type_of(receiver)?; + // 从实际类型开始向基类枚举可能声明当前函数的方法名。 let mut owner = actual; + // 向基类遍历;不假定候选函数一定是接收者当前生效的方法。 while let Some(definition) = owner { + // 读取类型载荷并验证句柄,后续方法表与基类访问都基于该借用。 let data = self.payload::(definition, "Type")?; + // 同一函数可出现在多个方法名下,各自可能承担不同能力契约。 for (name, candidate) in &data.methods { + // 这里只处理正在调用的函数身份,其余方法不检查。 if *candidate != function { continue; } self.check_typed_instance_storage(receiver)?; + // 普通 override 检查由调用方选择,能力约束不受该开关豁免。 if overrides { self.check_override(definition, name, function, None, false)?; } + // 实际方法查找仍选中该函数时按实际类型能力检查,否则按当前声明层处理。 let effective = if self .lookup_method(actual.expect("method receiver has a type"), name)? == Some(function) @@ -403,7 +503,9 @@ impl ObjectStore { } else { definition }; + // 有效类型继承的全部能力都参与,不能只检查第一个匹配能力。 for capability in self.inherited_traits(effective)? { + // 只有该能力声明当前方法的完整类型签名时才有约束可检查。 if let Some(expected) = self .payload::(capability, "Trait")? .signatures @@ -423,20 +525,29 @@ impl ObjectStore { /// Borrowed expected results for all typed capabilities of this effective /// method. The evaluator retains definitions before entering the body. + /// eval/NativeContext 收集有效方法的全部能力返回约束;返回类型句柄仍为借用。 pub fn method_trait_returns( &self, function: ObjectRef, receiver: ObjectRef, ) -> Result, VmError> { + // 接收者的有效类型可能经 type_target 转发;无类型时不会枚举名义方法。 let actual = self.type_of(receiver)?; + // 从实际类型开始向基类枚举可能声明当前函数的方法名。 let mut owner = actual; + // 累计所有能力返回约束,clone 不保留其中托管定义根。 let mut returns = Vec::new(); + // 向基类遍历;不假定候选函数一定是接收者当前生效的方法。 while let Some(definition) = owner { + // 读取类型载荷并验证句柄,后续方法表与基类访问都基于该借用。 let data = self.payload::(definition, "Type")?; + // 同一函数可出现在多个方法名下,各自可能承担不同能力契约。 for (name, candidate) in &data.methods { + // 这里只处理正在调用的函数身份,其余方法不检查。 if *candidate != function { continue; } + // 实际方法查找仍选中该函数时按实际类型能力检查,否则按当前声明层处理。 let effective = if self .lookup_method(actual.expect("method receiver has a type"), name)? == Some(function) @@ -445,8 +556,11 @@ impl ObjectStore { } else { definition }; + // 有效类型继承的全部能力都参与,不能只检查第一个匹配能力。 for capability in self.inherited_traits(effective)? { + // 读取能力签名表,名称用于返回错误的定位。 let contract = self.payload::(capability, "Trait")?; + // 只有该能力声明当前方法的完整类型签名时才有约束可检查。 if let Some(expected) = contract.signatures.get(name) { returns.push(( format!("{}.{}", contract.name, name), -- 2.52.0 From 7a40e85b9401b50bc4368da422683566882fe34b Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:36:56 +0800 Subject: [PATCH 12/62] docs(objects): explain type construction and definition lifecycle --- crates/objects/src/types.rs | 97 +++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/crates/objects/src/types.rs b/crates/objects/src/types.rs index 2232e09..67ccba9 100644 --- a/crates/objects/src/types.rs +++ b/crates/objects/src/types.rs @@ -1,3 +1,5 @@ +//! 类型、能力与实例共享同一托管堆;Store 定义身份/继承规则,VM 负责构造调用的执行调度。 +//! contracts 校验方法替换与能力契约,effects 查询当前快照的调用效果。 //! Nominal type identities in the same managed heap as their instances. #[path = "contracts.rs"] mod contracts; @@ -13,21 +15,28 @@ use crate::{ // A host type_target may read its receiver's raw attributes. Validate the // outer lookup after resolution without recursively re-entering its guard. struct AttributeCheckGuard<'a> { + /// 借用当前 Store 的属性检查递归表。 store: &'a ObjectStore, + /// 进入检查时的线程,用于隔离递归标记。 thread: std::thread::ThreadId, + /// 当前被检查对象,守卫不为其建立根。 object: ObjectRef, } impl Drop for AttributeCheckGuard<'_> { + /// 属性/类型重入检查的 RAII 清理,移除本线程当前对象,不清除其他活跃检查。 fn drop(&mut self) { + // 锁内定位递归检查表,守卫保证当前项此前已插入。 let mut checks = self .store .active_attribute_checks .lock() .expect("attribute checker poisoned"); + // 仅删除本线程该对象的检查项,嵌套其他对象仍保持活跃。 let active = checks .get_mut(&self.thread) .expect("active attribute check"); active.remove(&self.object); + // 线程内无剩余对象检查时移除空集合。 if active.is_empty() { checks.remove(&self.thread); } @@ -43,11 +52,13 @@ pub enum TypeConstructor { /// Opt-in allocator inheritance. The requested nominal type is prepended /// to the constructor arguments; the allocator must create that real type. NativeTyped(ObjectRef), + /// 禁止直接构造,源码派生沿用该策略后仍为抽象类型。 Abstract, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct ClassField { + /// 字段存储名称,源码类创建时检查重复及保留名。 pub name: String, /// Source annotation resolved at checked construction/field writes. pub type_name: String, @@ -58,60 +69,81 @@ pub struct ClassField { /// edges; no registry outside the heap keeps unused definitions alive. #[derive(Debug, Clone)] pub struct EndorTypeObject { + /// 诊断名称,名义身份仍由对象句柄决定。 name: String, + /// 固定基类强边,根 Object 为 None。 base: Option, + /// 实例创建策略,类型可调用路径按此分派。 constructor: TypeConstructor, + /// 方法名到函数的拥有边,更新走 define_method。 methods: BTreeMap, + /// 继承字段在前的完整构造布局。 fields: Vec, // Declaring execution environment; a managed edge, never an external root. + /// 自有字段注解的声明函数环境强边,不是外部根。 field_scope: Option, + /// 本层显式能力定义的强边,继承能力另沿基类查询。 traits: Vec, } impl EndorTypeObject { + /// 宿主/诊断借用类型声明名称,不代表仅按名称判断类型相等。 pub fn name(&self) -> &str { &self.name } + /// 继承查找借用固定基类句柄,不产生外部根。 pub fn base(&self) -> Option { self.base } + /// 构造器和宿主借用完整字段序列,包含从父类复制的字段。 pub fn fields(&self) -> &[ClassField] { &self.fields } } impl ObjectPayload for EndorTypeObject { + /// 类型定义不实现旧式原地赋值,允许只读局部重绑定引用。 fn reference_assignment_only(&self) -> bool { true } + /// Store 下转型为具体类型载荷的入口。 fn as_any(&self) -> &dyn std::any::Any { self } + /// 错误与默认诊断使用载荷类别,名义类型关系另外判断。 fn kind_name(&self) -> &'static str { "type" } + /// Heap 分配/受控更新类型时登记基类、构造器、字段环境、能力及方法强边。 fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { + // 基类本身是一条拥有边,不依赖堆外注册表维持存活。 if let Some(base) = self.base { visit(base); } + // 两类原生构造都持有函数强边;Record/Abstract 不包含函数。 if let TypeConstructor::Native(function) | TypeConstructor::NativeTyped(function) = self.constructor { visit(function); } + // 字段声明环境必须由类型拥有,否则稍后注解解析可能失去环境。 if let Some(scope) = self.field_scope { visit(scope); } + // 逐项登记已声明能力,重复引用也按拥有次数计算。 for capability in &self.traits { visit(*capability); } + // 方法表每个槽拥有函数强边,不因同一函数多名注册而去重。 for method in self.methods.values() { visit(*method); } } + /// Store 显示类型名称,不展开方法表或执行构造器。 fn repr(&self, _: ObjectRef) -> Result { Ok(format!("", self.name)) } + /// VM 把类型作为可调用对象时取得构造分派器;Type 可调用但不是函数分类。 fn native_callable(&self) -> Option> { Some(Arc::new(Construct)) } @@ -120,19 +152,24 @@ impl ObjectPayload for EndorTypeObject { #[derive(Debug)] struct Construct; impl NativeCallable for Construct { + /// NativeContext 构造入口:按类型的构造策略返回实例或委托 VM 调用分配器。 fn invoke( &self, cx: &mut NativeContext<'_>, arguments: &[Value], ) -> Result { + // 当前被调用对象就是类型,借用身份已由上下文保护。 let type_object = cx.callee(); + // 只读借用构造策略;下方修改前先复制需要跨修改使用的元数据。 let definition = cx.store().payload::(type_object, "Type")?; + // 原生委托交回 VM 帧循环,不能递归调用执行器;抽象类型明确拒绝。 match definition.constructor { TypeConstructor::Native(function) => Ok(NativeResult::Invoke { function, arguments: arguments.to_vec(), }), TypeConstructor::NativeTyped(function) => { + // NativeTyped 把实际请求类型放在首位,让继承分配器创建正确的派生载荷。 let mut values = Vec::with_capacity(arguments.len() + 1); values.push(Value::Object(type_object)); values.extend_from_slice(arguments); @@ -145,6 +182,7 @@ impl NativeCallable for Construct { Err(VmError::UnsupportedOperation("abstract type construction")) } TypeConstructor::Record => { + // Record 字段按位置一一对应,缺少或多余都在分配实例前拒绝。 if arguments.len() != definition.fields.len() { return Err(VmError::ArgumentCount { name: definition.name.clone(), @@ -153,11 +191,14 @@ impl NativeCallable for Construct { provided: arguments.len(), }); } + // 释放类型载荷借用后仍需要字段名,因此复制布局,不复制托管对象。 let fields = definition.fields.clone(); cx.store() .check_class_field_arguments(type_object, arguments)?; + // 分配临时根后再附着类型/写字段,失败时上下文统一清理半成品。 let instance = cx.new_object()?; cx.attach_type(instance, type_object)?; + // 按继承字段在前的布局依次写入,克隆 Value 不自动 retain,由属性拥有边承担持有。 for (field, value) in fields.iter().zip(arguments) { cx.set_attr(instance, &field.name, value.clone())?; } @@ -170,14 +211,18 @@ impl NativeCallable for Construct { /// Each field owns one external root; the caller must release both. #[derive(Debug, Clone, Copy)] pub struct RootTypes { + /// Object 根类型的一份外部拥有根,调用方须 release。 pub object: ObjectRef, + /// Type 元类型的一份外部拥有根,调用方须 release。 pub type_type: ObjectRef, } impl ObjectStore { /// Explicit bootstrap preserves the empty-store contract. Inheritance: /// Type -> Object; instance relations: Object : Type and Type : Type. + /// 宿主/VM 显式创建 Object/Type;不改变 Store::new 保持空堆的约定。 pub fn bootstrap_types(&mut self) -> Result { + // 先创建无元类型的 Object 定义,随后建立自举关系。 let object = self.allocate(EndorTypeObject { name: "Object".into(), base: None, @@ -187,8 +232,11 @@ impl ObjectStore { field_scope: None, traits: Vec::new(), })?; + // 记录第二次分配是否成功,便于后续任何错误清理已建对象。 let mut allocated_type = None; + // 闭包归拢 Type 分配与关系附着错误,外层统一释放自举临时根。 let result = (|| { + // Type 以 Object 为基类,随后自己以 Type 为类型,形成合法类型图环。 let type_type = self.allocate(EndorTypeObject { name: "Type".into(), base: Some(object), @@ -203,7 +251,9 @@ impl ObjectStore { self.attach_type(type_type, type_type)?; Ok(RootTypes { object, type_type }) })(); + // 自举失败归还已有根并收集可能形成的环,避免半初始化定义泄漏。 if result.is_err() { + // 只有成功分配 Type 后才需要归还其根。 if let Some(type_type) = allocated_type { self.release(type_type)?; } @@ -214,23 +264,29 @@ impl ObjectStore { } /// Base links are fixed at allocation, preventing inheritance cycles. + /// 宿主创建派生类型,复制父字段布局并沿用元类型;返回一份外部拥有根。 pub fn new_type( &mut self, name: impl Into, base: ObjectRef, constructor: TypeConstructor, ) -> Result { + // 父字段复制成新类型布局;后续不能私自改变继承前缀。 let fields = self .payload::(base, "Type")? .fields .clone(); + // 新类型继承基类的有效元类型,缺失元类型时拒绝构造。 let metatype = self .type_of(base)? .ok_or(VmError::InvalidArgument("base type has no metatype"))?; + // 持有名称用于新载荷与诊断,后续校验借用该字符串。 let name = name.into(); + // 类型名称不允许空串,但身份判断仍使用句柄而非文本。 if name.is_empty() { return Err(VmError::InvalidArgument("type name must not be empty")); } + // 分配时基类/构造器/方法环境等由 trace 登记边,返回一份外部根。 let definition = self.allocate(EndorTypeObject { name, base: Some(base), @@ -240,6 +296,7 @@ impl ObjectStore { field_scope: None, traits: Vec::new(), })?; + // 元类型附着失败则归还新定义根,不交付无元类型半成品。 if let Err(error) = self.attach_type(definition, metatype) { self.release(definition)?; return Err(error); @@ -247,12 +304,14 @@ impl ObjectStore { Ok(definition) } + /// 宿主/构造器先检查类型和能力存储约束,再交 Heap 建立不可替换的类型强边。 pub fn attach_type( &mut self, object: ObjectRef, type_object: ObjectRef, ) -> Result<(), VmError> { self.payload::(type_object, "Type")?; + // 逐项登记已声明能力,重复引用也按拥有次数计算。 for capability in self.inherited_traits(type_object)? { self.check_capability_storage(object, capability)?; } @@ -260,16 +319,23 @@ impl ObjectStore { } /// Borrowed type reference; legacy/untyped host objects return None. + /// VM/契约查询有效类型,沿 type_target 转发,最终返回头部的借用类型或 None。 pub fn type_of(&self, object: ObjectRef) -> Result, VmError> { + // 从门面对象出发追踪实际类型目标,不直接忽略 type_target 协议。 let mut current = object; + // 保存已访问身份,防宿主转发环导致无限循环。 let mut visited = std::collections::BTreeSet::new(); + // 逐跳验证存活句柄和载荷,直到无类型转发目标。 loop { + // 同一身份再出现说明转发环,返回明确协议错误。 if !visited.insert(current) { return Err(VmError::InvalidArgument( "cyclic shared-reference type target", )); } + // 分配临时根后再附着类型/写字段,失败时上下文统一清理半成品。 let instance = self.object(current)?; + // 有转发目标则继续,无目标时读取当前对象头部名义类型。 match instance.payload().type_target(self)? { Some(target) => current = target, None => return Ok(instance.header().type_object()), @@ -277,11 +343,16 @@ impl ObjectStore { } } + /// 注解/替换检查沿单继承链比较名义身份,包含类型自身。 pub fn is_subtype(&self, candidate: ObjectRef, expected: ObjectRef) -> Result { self.payload::(expected, "Type")?; + // 从候选类型本身开始,因此自反子类型关系成立。 let mut current = Some(candidate); + // 基类在创建时固定且指向已有类型,正常类型图不会出现继承环。 while let Some(type_object) = current { + // 借用类型载荷验证身份和种类,再读字段、基类或方法表。 let definition = self.payload::(type_object, "Type")?; + // 比较完整名义身份,不接受仅同名的另一个类型。 if type_object == expected { return Ok(true); } @@ -290,8 +361,10 @@ impl ObjectStore { Ok(false) } + /// VM/宿主判断实例有效类型是否为 expected 的子类型,无类型旧对象返回 false。 pub fn is_instance(&self, object: ObjectRef, expected: ObjectRef) -> Result { self.payload::(expected, "Type")?; + // 有效类型可来自转发对象;无类型不是 InvalidHandle,而是正常 false。 match self.type_of(object)? { Some(type_object) => self.is_subtype(type_object, expected), None => Ok(false), @@ -299,6 +372,7 @@ impl ObjectStore { } /// Updating the managed method table preserves nominal identity and base. + /// 宿主/VM 受控更新方法表,身份/基类不变,先验证覆盖及能力约束再替换载荷。 pub fn define_method( &mut self, type_object: ObjectRef, @@ -306,20 +380,26 @@ impl ObjectStore { function: ObjectRef, ) -> Result<(), VmError> { self.object(function)?; + // 持有名称用于新载荷与诊断,后续校验借用该字符串。 let name = name.into(); + // 类型名称不允许空串,但身份判断仍使用句柄而非文本。 if name.is_empty() || name == "type" { return Err(VmError::InvalidArgument("reserved or empty method name")); } + // 借用类型载荷验证身份和种类,再读字段、基类或方法表。 let definition = self.payload::(type_object, "Type")?; + // 实例字段与类型方法不能同名,避免构造/查找产生歧义。 if definition.fields.iter().any(|field| field.name == name) { return Err(VmError::InvalidArgument( "method conflicts with class field", )); } + // 先准备独立方法表副本,契约失败时真实载荷保持原状。 let mut methods = definition.methods.clone(); self.validate_override_update(type_object, &name, function)?; self.validate_method_update(type_object, &name, function)?; methods.insert(name, function); + // 保留名称、基类、构造、字段和能力,只替换方法表;Heap 再更新强边和版本。 let updated = EndorTypeObject { name: definition.name.clone(), base: definition.base, @@ -335,6 +415,7 @@ impl ObjectStore { /// Atomically build a source class, including inherited constructor fields. /// Only explicitly inheritable native allocators are reused. Abstract /// subclasses remain abstract; no Record can forge a native payload. + /// eval/宿主创建源码类的无字段声明环境入口,转交统一构造路径。 pub fn new_class( &mut self, name: impl Into, @@ -347,6 +428,7 @@ impl ObjectStore { /// Source class builder with a traced function environment for field names. /// None supports primitive annotations and the declaring class itself. + /// eval 创建源码类,组合继承字段、当前方法与声明环境,完成契约检查后才交付根。 pub fn new_class_with_scope( &mut self, name: impl Into, @@ -355,19 +437,27 @@ impl ObjectStore { methods: BTreeMap, scope: Option, ) -> Result { + // 字段声明环境必须由类型拥有,否则稍后注解解析可能失去环境。 if let Some(scope) = scope { self.payload::(scope, "function")?; } + // 没有自有字段时无需持有当前声明环境,继承字段仍通过其声明层解析。 let field_scope = if own_fields.is_empty() { None } else { scope }; + // 借用类型载荷验证身份和种类,再读字段、基类或方法表。 let definition = self.payload::(base, "Type")?; + // Native 未显式允许源码继承分配,不能用普通 Record 伪造原生载荷。 if matches!(definition.constructor, TypeConstructor::Native(_)) { return Err(VmError::UnsupportedOperation( "native allocator does not opt into source subclass construction", )); } + // 源码派生沿用允许继承的策略,Abstract 仍为 Abstract,NativeTyped 得到请求类型。 let constructor = definition.constructor; + // 继承字段保持前缀,再按源码顺序附加新字段。 let mut fields = definition.fields.clone(); + // 逐个检查重复、保留名及继承方法冲突,不允许覆盖已有字段布局。 for field in own_fields { + // 名称与注解都不能空,字段不能重复或遮蔽父方法。 if field.name.is_empty() || field.type_name.is_empty() || field.name == "type" @@ -380,21 +470,27 @@ impl ObjectStore { } fields.push(field); } + // 方法提交前验证函数句柄,拒绝与完整继承字段序列重名。 for (name, method) in &methods { self.object(*method)?; + // 类型名称不允许空串,但身份判断仍使用句柄而非文本。 if name.is_empty() || name == "type" || fields.iter().any(|field| field.name == *name) { return Err(VmError::InvalidArgument( "method conflicts with class field", )); } } + // 新类型继承基类的有效元类型,缺失元类型时拒绝构造。 let metatype = self .type_of(base)? .ok_or(VmError::InvalidArgument("base type has no metatype"))?; + // 持有名称用于新载荷与诊断,后续校验借用该字符串。 let name = name.into(); + // 类型名称不允许空串,但身份判断仍使用句柄而非文本。 if name.is_empty() { return Err(VmError::InvalidArgument("type name must not be empty")); } + // 一次构造完整类载荷,后面的验证失败只需释放这份新根。 let class = self.allocate(EndorTypeObject { name, base: Some(base), @@ -404,6 +500,7 @@ impl ObjectStore { field_scope, traits: Vec::new(), })?; + // 继承能力、普通覆盖和元类型附着按顺序完成,任一失败都不交付该类。 if let Err(error) = self .validate_inherited_traits(class) .and_then(|()| self.validate_class_overrides(class)) -- 2.52.0 From 2e48e7455cf8972f6cf6106c68a1894c05c30a96 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:38:31 +0800 Subject: [PATCH 13/62] docs(objects): explain method binding and capability construction --- crates/objects/src/types.rs | 87 ++++++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 2 deletions(-) diff --git a/crates/objects/src/types.rs b/crates/objects/src/types.rs index 67ccba9..be52c28 100644 --- a/crates/objects/src/types.rs +++ b/crates/objects/src/types.rs @@ -512,14 +512,18 @@ impl ObjectStore { Ok(class) } + /// Store/VM/能力检查沿继承链查最近同名方法,返回借用函数,不自动绑定接收者。 pub fn lookup_method( &self, type_object: ObjectRef, name: &str, ) -> Result, VmError> { + // 从类型自身向父链查找,最近声明优先。 let mut current = Some(type_object); + // 基类固定于创建,逐层校验类型句柄而不是按名称检索。 while let Some(type_object) = current { let definition = self.payload::(type_object, "Type")?; + // 找到方法即返回借用身份,不替调用方 retain。 if let Some(function) = definition.methods.get(name) { return Ok(Some(*function)); } @@ -530,15 +534,22 @@ impl ObjectStore { /// Source positional parameters after receiver binding. None means the /// callable uses a native protocol without a source signature. + /// 旧式能力检查/宿主查询源码参数总数,扣除所有绑定接收者;原生协议返回 None。 pub fn positional_parameter_count( &self, function: ObjectRef, ) -> Result, VmError> { + // 从传入可调用对象开始逐层展开 BoundMethod。 let mut current = function; + // 累计已绑定接收者槽数,不能只扣一层。 let mut receivers = 0usize; + // 按链迭代而非递归,未知原生元数据保持 None。 loop { + // 通过 object 验证句柄并记录读取,随后仅借用载荷元数据。 let payload = self.object(current)?.payload(); + // 每层绑定都额外供应一个接收者,继续查询真实目标函数。 if let Some(bound) = payload.as_any().downcast_ref::() { + // 计数使用 checked_add,防极深包装使参数扣减绕回。 receivers = receivers .checked_add(1) .ok_or(VmError::ExecutionLimit("bound receivers"))?; @@ -547,6 +558,7 @@ impl ObjectStore { .as_any() .downcast_ref::() { + // 源码参数列表包含显式 self,默认值不改变总参数槽数。 let count = self .payload::(function.code(), "code")? .code() @@ -564,16 +576,21 @@ impl ObjectStore { } } - /// Validated direct native metadata. Bound methods delegate to their actual - /// target, so receiver slots remain explicit here. Returned handles are borrowed. + /// 查询直接原生元数据;完整签名保留显式接收者,返回类型句柄是借用。 + /// BoundMethod 本身没有完整签名,参数区间查询通过 call_arity 另行解包。 + /// 原生调用/契约分析查询当前载荷完整签名并校验;不会在这里展开 BoundMethod。 pub fn native_signature( &self, function: ObjectRef, ) -> Result, VmError> { + // 通过 object 验证句柄并记录读取,随后仅借用载荷元数据。 let payload = self.object(function)?.payload(); + // 查询当前载荷提供的签名快照,不执行回调,也不新增定义根。 let signature = payload.call_signature(); + // 存在完整签名才校验全部类型边及数量元数据一致性。 if let Some(signature) = &signature { signature.validate(self)?; + // 载荷若同时发布 arity,必须与完整签名完全一致。 if payload .call_arity() .is_some_and(|arity| arity != signature.arity) @@ -588,28 +605,38 @@ impl ObjectStore { /// native wrappers and source call graphs with unproved operations remain unknown. /// This query follows current globals, closure Cells and known receivers without /// executing code, freezing targets or retaining a cross-query cache. + /// VM/契约通过 effects 分析当前调用图快照,None 表示无法证明整体效果。 pub fn call_effect(&self, function: ObjectRef) -> Result, VmError> { self.infer_call_effect(function) } /// Unified source/native positional interval, after any bound receivers. /// None means no published native metadata (including generic Type construction). + /// VM 数量检查统一查询源码/原生区间,先解绑定方法再扣除接收者。 pub fn call_arity(&self, function: ObjectRef) -> Result, VmError> { + // 从传入可调用对象开始逐层展开 BoundMethod。 let mut current = function; + // 累计已绑定接收者槽数,不能只扣一层。 let mut receivers = 0usize; + // 按链迭代而非递归,未知原生元数据保持 None。 loop { + // 通过 object 验证句柄并记录读取,随后仅借用载荷元数据。 let payload = self.object(current)?.payload(); + // 每层绑定都额外供应一个接收者,继续查询真实目标函数。 if let Some(bound) = payload.as_any().downcast_ref::() { + // 计数使用 checked_add,防极深包装使参数扣减绕回。 receivers = receivers .checked_add(1) .ok_or(VmError::ExecutionLimit("bound receivers"))?; current = bound.function; continue; } + // 源码区间由总参数数减默认值数;原生优先完整签名,否则兼容独立 arity。 let arity = if let Some(function) = payload .as_any() .downcast_ref::() { + // 源码代码布局已经校验,函数 defaults 数量也经构造校验。 let maximum = self .payload::(function.code(), "code")? .code() @@ -626,7 +653,9 @@ impl ObjectStore { }; return arity .map(|arity| { + // 先校验原始区间,再调整已绑定接收者造成的可见数量。 let arity = arity.validate()?; + // 开放上界保持 usize::MAX,有限上界 checked_sub 防绑定超出参数容量。 let maximum = if arity.maximum == usize::MAX { usize::MAX } else { @@ -647,9 +676,12 @@ impl ObjectStore { } /// Returns one owned root, including for a newly bound inherited method. + /// eval LoadAttr/宿主获取可用属性;对象值 retain 一根,方法则分配拥有接收者和函数的绑定对象。 pub fn bind_attr(&mut self, receiver: ObjectRef, name: &str) -> Result { + // 借用查找与拥有结果分开:返回到执行栈的对象必须先取得根。 match self.lookup_attr(receiver, name)? { AttributeLookup::Value(value) => { + // 标量由 Rust 值直接持有,仅对象属性结果需要 retain。 if let Value::Object(object) = value { self.retain(object)?; } @@ -661,43 +693,54 @@ impl ObjectStore { } } + /// Store get_attr/bind_attr 与效果分析共用借用查找,不分配 BoundMethod 或创建结果根。 pub(crate) fn lookup_attr( &self, receiver: ObjectRef, name: &str, ) -> Result { + // 保留最初 receiver 身份,用于名义元数据、能力遮蔽检查与方法绑定。 let object = self.object(receiver)?; + // 已附着类型优先作为保留属性暴露,返回借用类型句柄。 if name == "type" && let Some(type_object) = self.type_of(receiver)? { return Ok(AttributeLookup::Value(Value::Object(type_object))); } + // 能力名称元数据优先于普通字段,但仍受方法遮蔽保护。 if name == "name" && let Some(capability) = object.payload().as_any().downcast_ref::() { self.check_typed_method_write(receiver, name)?; return Ok(AttributeLookup::Value(Value::Text(capability.name.clone()))); } + // 类型对象先处理自身 name/base 元数据和类方法表,不绑定为实例方法。 if let Some(definition) = object.payload().as_any().downcast_ref::() { + // 根类型 base 为 Nil,与没有这个元数据属性不同。 let metadata = match name { "name" => Some(Value::Text(definition.name.clone())), "base" => Some(definition.base.map(Value::Object).unwrap_or(Value::Nil)), _ => None, }; + // 元数据返回前核对类型化能力方法不能被同名属性遮蔽。 if let Some(value) = metadata { self.check_typed_method_write(receiver, name)?; return Ok(AttributeLookup::Value(value)); } + // 直接从类型对象取方法返回函数值,不绑定该类型为 self。 if let Some(function) = self.lookup_method(receiver, name)? { self.check_typed_method_write(receiver, name)?; return Ok(AttributeLookup::Value(Value::Object(function))); } } + // 普通字段沿 attribute_target 找最终存储,成功路径必非空。 let storage = *self .attribute_path(receiver)? .last() .expect("nonempty attribute path"); + // 存储缺字段/不允许属性时还可尝试实例类型方法;其他错误直接传播。 match self.heap.get_attr(storage, name) { + // 字段命中仍检查能力方法遮蔽,然后返回借用值。 Ok(value) => { self.check_typed_method_write(receiver, name)?; return Ok(AttributeLookup::Value(value)); @@ -705,7 +748,9 @@ impl ObjectStore { Err(VmError::AttributeNotFound(_) | VmError::UnsupportedOperation("attributes")) => {} Err(error) => return Err(error), } + // 根据接收者有效类型查实例方法,绑定的是原始 receiver 身份。 if let Some(type_object) = self.type_of(receiver)? { + // 实例方法返回函数/接收者对,由 bind_attr 决定是否分配绑定对象。 if let Some(function) = self.lookup_method(type_object, name)? { return Ok(AttributeLookup::Method { function, receiver }); } @@ -717,6 +762,7 @@ impl ObjectStore { } } +/// 内部借用查找结果;Method 延迟绑定,使只读分析不必分配 BoundMethod。 pub(crate) enum AttributeLookup { Value(Value), Method { @@ -726,27 +772,36 @@ pub(crate) enum AttributeLookup { } #[derive(Debug)] +/// 持有函数和原接收者的托管包装,由 bind_attr 分配一份外部根。 struct BoundMethod { + // 原始接收者强边,不一定是最终属性存储对象。 receiver: ObjectRef, + // 底层函数强边,委托调用时由 NativeContext 取得输出根。 function: ObjectRef, } impl ObjectPayload for BoundMethod { + /// 绑定方法按函数分类,可在 Store 分配时附着 Function 类型。 fn is_function(&self) -> bool { true } + /// 绑定方法/能力定义不实现原地赋值,允许只读局部重绑定引用。 fn reference_assignment_only(&self) -> bool { true } + /// Store 的载荷下转型接口,不是语言名义类型判断。 fn as_any(&self) -> &dyn std::any::Any { self } + /// 错误/诊断载荷类别。 fn kind_name(&self) -> &'static str { "bound-method" } + /// Heap 分配/更新载荷时枚举拥有边,重复边仍逐条报告。 fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { visit(self.receiver); visit(self.function); } + /// VM 取得绑定方法分派器,调用时由上下文保护其载荷边。 fn native_callable(&self) -> Option> { Some(Arc::new(CallBoundMethod)) } @@ -755,14 +810,17 @@ impl ObjectPayload for BoundMethod { #[derive(Debug)] struct CallBoundMethod; impl NativeCallable for CallBoundMethod { + /// VM 分派绑定方法,把接收者插在显式参数前并委托底层函数,不递归运行执行器。 fn invoke( &self, cx: &mut NativeContext<'_>, arguments: &[Value], ) -> Result { + // 绑定载荷中的强边由上下文保护,回调只借用其元数据。 let bound = cx .store() .payload::(cx.callee(), "bound method")?; + // 新参数向量先放 self 再放调用方参数,clone 本身不 retain。 let mut values = Vec::with_capacity(arguments.len() + 1); values.push(Value::Object(bound.receiver)); values.extend_from_slice(arguments); @@ -787,56 +845,73 @@ pub struct TypedMethodRequirement { pub signature: crate::CallSignature, } #[derive(Debug)] +/// 显式能力定义:最小数量要求与可选完整签名同属托管载荷,定义本身不可任意替换。 pub struct EndorTraitObject { name: String, requirements: Vec, signatures: BTreeMap, } impl EndorTraitObject { + /// 宿主/诊断借用能力名称,能力相等仍按身份。 pub fn name(&self) -> &str { &self.name } + /// 能力校验借用最小位置参数要求列表,不含绑定 self。 pub fn requirements(&self) -> &[MethodRequirement] { &self.requirements } + /// 契约校验借用指定方法完整签名,含显式 self,未声明返回 None。 pub fn signature(&self, name: &str) -> Option<&crate::CallSignature> { self.signatures.get(name) } } impl ObjectPayload for EndorTraitObject { + /// Heap 分配/更新载荷时枚举拥有边,重复边仍逐条报告。 fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { + // 能力完整签名的全部类型定义参与 trace,不只看当前调用到的分支。 for signature in self.signatures.values() { + // 每个引用都是拥有边,不能按身份去重。 for reference in signature.references() { visit(reference); } } } + /// 绑定方法/能力定义不实现原地赋值,允许只读局部重绑定引用。 fn reference_assignment_only(&self) -> bool { true } + /// Store 的载荷下转型接口,不是语言名义类型判断。 fn as_any(&self) -> &dyn std::any::Any { self } + /// 错误/诊断载荷类别。 fn kind_name(&self) -> &'static str { "trait" } + /// Store 显示能力名称,不执行方法或展开签名。 fn repr(&self, _: ObjectRef) -> Result { Ok(format!("", self.name)) } } impl ObjectStore { + /// VM/宿主创建只约束方法存在与参数数量的能力,返回一份外部根。 pub fn new_trait( &mut self, name: impl Into, requirements: Vec, ) -> Result { + // 持有能力名称,禁止空名称但不把名称作为名义身份。 let name = name.into(); + // 只约束列表内部去重,避免重复方法要求。 let mut names = std::collections::BTreeSet::new(); + // 能力名称必须非空。 if name.is_empty() { return Err(VmError::InvalidArgument("trait name must not be empty")); } + // 最小能力要求不含 self,usize::MAX 无法再加 self,提前拒绝。 for requirement in &requirements { + // 拒绝空/保留名、无法加接收者的数量与重复要求。 if requirement.name.is_empty() || requirement.name == "type" || requirement.parameters == usize::MAX @@ -854,20 +929,28 @@ impl ObjectStore { }) } + /// VM/宿主创建显式参数/返回/效果能力,要求含 self 的固定规范签名。 pub fn new_typed_trait( &mut self, name: impl Into, methods: Vec, ) -> Result { + // 持有能力名称,禁止空名称但不把名称作为名义身份。 let name = name.into(); + // 能力名称必须非空。 if name.is_empty() { return Err(VmError::InvalidArgument("trait name must not be empty")); } + // 完整方法签名按名保存,同时派生兼容的最小参数要求。 let mut signatures = BTreeMap::new(); + // 每份规范完整签名生成一个不含 self 的参数数量要求。 let mut requirements = Vec::new(); + // 全部元数据验证完后才分配能力,失败不产生半成品。 for method in methods { method.signature.validate(self)?; + // 类型化能力使用固定且至少含一接收者的规范签名,不接受可选接收者。 let arity = method.signature.arity; + // 数量必须 required==maximum 且有限,方法名也不能重复或占用 type。 if method.name.is_empty() || method.name == "type" || arity.required == 0 -- 2.52.0 From e5da8055e0f84d281e3efb8e192e6de8b82a1514 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:40:20 +0800 Subject: [PATCH 14/62] docs(objects): explain capability conformance and annotation resolution --- crates/objects/src/types.rs | 127 +++++++++++++++++++++++++++++++++++- 1 file changed, 125 insertions(+), 2 deletions(-) diff --git a/crates/objects/src/types.rs b/crates/objects/src/types.rs index be52c28..cbd58e0 100644 --- a/crates/objects/src/types.rs +++ b/crates/objects/src/types.rs @@ -975,16 +975,22 @@ impl ObjectStore { }) } + /// 类型附着、字段/方法更新和能力查询共用:沿父链收集去重的借用能力身份。 fn inherited_traits(&self, definition: ObjectRef) -> Result, VmError> { + // 去重能力身份,不建立堆外拥有注册表或延长能力寿命。 let mut found = std::collections::BTreeSet::new(); + // 从类型自身开始,继承能力和本层能力一起核对。 let mut current = Some(definition); + // 沿固定基类链逐层读取,不按名称猜测类型。 while let Some(definition) = current { + // 验证类型载荷后才读取其基类或能力列表。 let definition = self.payload::(definition, "Type")?; found.extend(definition.traits.iter().copied()); current = definition.base; } Ok(found.into_iter().collect()) } + /// 能力验证单项方法存在及数量,完整签名存在时再调用 contracts 的替换检查。 fn validate_requirement( &self, capability: ObjectRef, @@ -993,6 +999,7 @@ impl ObjectStore { definition: ObjectRef, defer_unknown: bool, ) -> Result<(), VmError> { + // 构造带能力名/方法名的错误,调用方不需要解析诊断文本定位失败。 let invalid = |reason| VmError::TraitViolation { capability: self .payload::(capability, "Trait") @@ -1002,15 +1009,19 @@ impl ObjectStore { method: requirement.name.clone(), reason, }; + // 能力要求的方法必须实际存在,不能以缺少元数据替代。 let function = function.ok_or_else(|| invalid("required method is missing"))?; + // 查询函数可见数量约束;缺失原生数量元数据不能证明满足能力。 let arity = self .call_arity(function)? .ok_or_else(|| invalid("required member has no positional signature"))?; + // 能力最小数量不含 self,校验时加一个显式接收者。 if !arity.accepts(requirement.parameters + 1) { return Err(invalid( "positional parameter count does not match (including self)", )); } + // 完整签名存在时还要验证参数/返回/效果,不只检查数量。 if let Some(expected) = self .payload::(capability, "Trait")? .signatures @@ -1027,14 +1038,18 @@ impl ObjectStore { } Ok(()) } + /// 能力声明/查询检查全部要求,递归定义通过 with_trait_check 防止无限重入。 fn validate_trait( &self, definition: ObjectRef, capability: ObjectRef, defer_unknown: bool, ) -> Result<(), VmError> { + // 递归检查对按定义/能力区分,回调结束后 RAII 清理标记。 self.with_trait_check(definition, capability, || { + // 能力载荷先验证,再借用名称/完整签名/最小要求。 let contract = self.payload::(capability, "Trait")?; + // 遍历能力全部要求,任一失败阻止声明/更新。 for requirement in &contract.requirements { self.validate_requirement( capability, @@ -1047,7 +1062,9 @@ impl ObjectStore { Ok(()) }) } + /// 源码类创建后检查所有继承能力,允许暂缓尚未解析的源码注解。 fn validate_inherited_traits(&self, definition: ObjectRef) -> Result<(), VmError> { + // 全部继承能力都参与,不以首个成功项短路。 for capability in self.inherited_traits(definition)? { self.validate_trait(definition, capability, true)?; } @@ -1055,9 +1072,12 @@ impl ObjectStore { } // Enumerate borrowed heap handles only while validating a definition update; // no global type registry or owning descendant index is introduced. + /// 方法/能力更新前扫描存活类型,返回包含 base 自身的全部子类型借用身份。 fn descendant_types(&self, base: ObjectRef) -> Result, VmError> { self.payload::(base, "Type")?; + // 去重能力身份,不建立堆外拥有注册表或延长能力寿命。 let mut found = vec![]; + // 扫描当前存活对象;只选类型载荷及其名义子类型。 for reference in self.heap.live_references() { if self .object(reference)? @@ -1071,34 +1091,46 @@ impl ObjectStore { } Ok(found) } + /// define_method 提交前只检查真实会受该父方法变化影响的后代能力要求。 fn validate_method_update( &self, changed: ObjectRef, name: &str, function: ObjectRef, ) -> Result<(), VmError> { + // 父定义变更必须考虑现有后代,不能只核对当前类型。 for descendant in self.descendant_types(changed)? { + // 从后代向父层寻找当前方法的实际来源。 let mut current = Some(descendant); + // 记录是否在遇到更近覆盖前到达 changed。 let mut affected = false; + // 沿固定基类链逐层读取,不按名称猜测类型。 while let Some(definition) = current { + // 查找会使用此次变更的方法,该后代属于影响范围。 if definition == changed { affected = true; break; } + // 验证类型载荷后才读取其基类或能力列表。 let definition = self.payload::(definition, "Type")?; + // 更近后代已覆盖同名方法,父层更新不会改变该后代查找结果。 if definition.methods.contains_key(name) { break; } current = definition.base; } + // 未受查找影响的后代跳过能力重验。 if !affected { continue; } + // 全部继承能力都参与,不以首个成功项短路。 for capability in self.inherited_traits(descendant)? { + // 遍历能力全部要求,任一失败阻止声明/更新。 for requirement in &self .payload::(capability, "Trait")? .requirements { + // 只校验当前被替换方法对应要求,其他方法表项没有变化。 if requirement.name == name { self.validate_requirement( capability, @@ -1113,23 +1145,27 @@ impl ObjectStore { } Ok(()) } + /// 宿主/VM 显式声明能力;先验证后代和已有实例,再提交定义强边。 pub fn implement_trait( &mut self, definition: ObjectRef, capability: ObjectRef, ) -> Result<(), VmError> { self.payload::(capability, "Trait")?; + // 父定义变更必须考虑现有后代,不能只核对当前类型。 for descendant in self.descendant_types(definition)? { self.validate_trait(descendant, capability, false)?; } - // Existing objects must satisfy the same storage invariant as new writes. - // Borrowed heap enumeration adds no owning instance registry. + // 已有实例也必须满足新写入的存储约束;堆枚举只借用,不延长实例寿命。 + // 类型化能力才需要扫描实例存储,最小数量能力没有遮蔽签名要求。 if !self .payload::(capability, "Trait")? .signatures .is_empty() { + // 枚举已有实例检查同一存储不变量,不建立持有实例的注册表。 for object in self.heap.live_references() { + // 按有效类型判断是否属于被声明类型及后代,兼容类型转发门面。 if let Some(actual) = self.type_of(object)? && self.is_subtype(actual, definition)? { @@ -1137,20 +1173,25 @@ impl ObjectStore { } } } + // 复制定义准备提交;此前检查失败不改变真实能力列表。 let mut updated = self.payload::(definition, "Type")?.clone(); + // 重复声明是无操作,首次声明才更新载荷边与版本。 if !updated.traits.contains(&capability) { updated.traits.push(capability); self.heap.replace_payload(definition, updated)?; } Ok(()) } + /// 名义能力查询要求显式声明/继承,命中后重验当前方法,不以结构相似猜测。 pub fn implements_trait( &self, definition: ObjectRef, capability: ObjectRef, ) -> Result { self.payload::(capability, "Trait")?; + // 显式声明或继承才算支持能力,方法形状相同也不会自动成为声明。 let declared = self.inherited_traits(definition)?.contains(&capability); + // 命中声明后重验当前方法/实例,避免把历史声明当作永久有效证明。 if declared { self.validate_trait(definition, capability, false)?; } @@ -1158,12 +1199,15 @@ impl ObjectStore { } /// Typed capability methods belong to the method table. Instance fields /// cannot replace them independently of dispatch through that table. + /// Store 属性读写及元数据查找防止字段遮蔽类型化能力方法,带按线程递归守卫。 pub(crate) fn check_typed_method_write( &self, object: ObjectRef, name: &str, ) -> Result<(), VmError> { + // 递归属性检查按线程隔离。 let thread = std::thread::current().id(); + // 同线程同对象检查重入时返回,避免 type_target 读取自身属性导致无限递归。 if !self .active_attribute_checks .lock() @@ -1174,14 +1218,19 @@ impl ObjectStore { { return Ok(()); } + // 成功登记后建立守卫,错误和正常返回都清理本次对象检查标记。 let _guard = AttributeCheckGuard { store: self, thread, object, }; + // 仅有有效名义类型时才有继承能力可检查。 if let Some(definition) = self.type_of(object)? { + // 全部继承能力都参与,不以首个成功项短路。 for capability in self.inherited_traits(definition)? { + // 能力载荷先验证,再借用名称/完整签名/最小要求。 let contract = self.payload::(capability, "Trait")?; + // 实例字段不得取代能力方法表分派,拒绝同名属性遮蔽。 if contract.signatures.contains_key(name) { return Err(VmError::TraitViolation { capability: contract.name.clone(), @@ -1194,26 +1243,34 @@ impl ObjectStore { Ok(()) } + /// 类型附着/能力声明/宿主分派前核对实际属性存储及固有属性不遮蔽能力方法。 fn check_capability_storage( &self, object: ObjectRef, capability: ObjectRef, ) -> Result<(), VmError> { + // 能力载荷先验证,再借用名称/完整签名/最小要求。 let contract = self.payload::(capability, "Trait")?; + // 没有完整签名时无此处的类型化方法存储不变量。 if contract.signatures.is_empty() { return Ok(()); } + // 沿属性转发链确定实际存储终点,attribute_path 成功时链必非空。 let storage = *self .attribute_path(object)? .last() .expect("nonempty attribute path"); + // 检查每个类型化方法名的固有元数据及存储属性冲突。 for name in contract.signatures.keys() { + // 查询原对象固有属性身份,而不是只查看转发终点。 let payload = self.object(object)?.payload(); + // Type 的 name/base/类方法与 Trait 的 name 都是固有属性,可能与能力方法冲突。 let intrinsic = if payload.as_any().is::() { name == "name" || name == "base" || self.lookup_method(object, name)?.is_some() } else { payload.as_any().is::() && name == "name" }; + // 固有属性无法经普通存储删除,声明该冲突能力必须失败。 if intrinsic { return Err(VmError::TraitViolation { capability: contract.name.clone(), @@ -1221,6 +1278,7 @@ impl ObjectStore { reason: "intrinsic attribute shadows a typed capability method", }); } + // 字段命中即冲突;不存在或不允许属性可继续,其他错误传播。 match self.heap.get_attr(storage, name) { Ok(_) => { return Err(VmError::TraitViolation { @@ -1240,9 +1298,13 @@ impl ObjectStore { /// Method names whose instance storage participates in typed capability /// validity. Hosts can subscribe to these attribute keys when caching. + /// 宿主缓存查询当前类型化能力涉及的属性键,返回去重名称,不建立订阅。 pub fn typed_method_names(&self, object: ObjectRef) -> Result, VmError> { + // 按名称去重,多个能力共用方法名只返回一次。 let mut names = std::collections::BTreeSet::new(); + // 仅有有效名义类型时才有继承能力可检查。 if let Some(definition) = self.type_of(object)? { + // 全部继承能力都参与,不以首个成功项短路。 for capability in self.inherited_traits(definition)? { names.extend( self.payload::(capability, "Trait")? @@ -1257,8 +1319,11 @@ impl ObjectStore { /// Validate instance storage before host dispatch that bypasses attribute /// lookup. Raw storage access is trusted but cannot certify conformance. + /// 绕过普通属性查找的宿主分派必须显式重验实例存储,原始载荷读取不能证明符合能力。 pub fn check_typed_instance_storage(&self, object: ObjectRef) -> Result<(), VmError> { + // 仅有有效名义类型时才有继承能力可检查。 if let Some(definition) = self.type_of(object)? { + // 全部继承能力都参与,不以首个成功项短路。 for capability in self.inherited_traits(definition)? { self.check_capability_storage(object, capability)?; } @@ -1266,11 +1331,15 @@ impl ObjectStore { Ok(()) } + /// 值级能力查询:先验证有效类型显式声明,再检查实例实际存储,旧无类型对象为 false。 pub fn supports(&self, object: ObjectRef, capability: ObjectRef) -> Result { self.payload::(capability, "Trait")?; + // 无有效类型返回 false,有类型还需显式能力声明及实例存储检查。 match self.type_of(object)? { Some(definition) => { + // 显式声明或继承才算支持能力,方法形状相同也不会自动成为声明。 let declared = self.implements_trait(definition, capability)?; + // 命中声明后重验当前方法/实例,避免把历史声明当作永久有效证明。 if declared { self.check_capability_storage(object, capability)?; } @@ -1285,19 +1354,31 @@ impl ObjectStore { /// while validating the return, so rebinding globals cannot release it mid-call. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AnnotationType { + /// 内建 Object 注解,接受所有有效值,不等于特定 Object 名义定义。 Any, + /// 只接受 Value::Nil。 Unit, + /// 接受直接 Number 或堆数值载荷。 Number, + /// 接受直接 Text 或堆字符串载荷。 String, + /// 只接受直接 Bool。 Bool, + /// 按载荷 is_function 判定,不接受所有可调用 Type。 Function, + /// 要求 Type 载荷。 Type, + /// 要求 Trait 载荷。 Trait, + /// 借用名义类型定义,按实例继承关系匹配。 Nominal(ObjectRef), + /// 借用显式能力定义,按 supports 完整检查。 Capability(ObjectRef), } impl AnnotationType { + /// 帧/签名 trace 和保护逻辑取得注解携带的借用定义引用,直接类别没有引用。 pub fn reference(self) -> Option { + // 只有 Nominal/Capability 携带定义强边来源,其余注解是直接类别。 match self { Self::Nominal(reference) | Self::Capability(reference) => Some(reference), _ => None, @@ -1305,7 +1386,9 @@ impl AnnotationType { } } impl ObjectStore { + /// 源码字段/函数解析内建注解名,优先于环境同名绑定;Text 与 String 同义。 fn primitive_annotation(name: &str) -> Option { + // 内建注解直接映射固定类别,不访问语言全局空间。 match name { "Object" => Some(AnnotationType::Any), "Unit" => Some(AnnotationType::Unit), @@ -1319,6 +1402,7 @@ impl ObjectStore { } } + /// Store 字段写入/删除按对象有效类型查字段真实声明层,返回借用定义及字段。 pub(crate) fn declared_field( &self, object: ObjectRef, @@ -1327,17 +1411,22 @@ impl ObjectStore { self.declared_field_in_type(self.type_of(object)?, name) } + /// 字段验证沿继承链只搜索每层自有后缀,使继承字段使用原声明环境。 fn declared_field_in_type( &self, mut current: Option, name: &str, ) -> Result, VmError> { + // 沿固定基类链逐层读取,不按名称猜测类型。 while let Some(definition) = current { + // 先验证定义/函数载荷,再访问字段表或声明环境。 let data = self.payload::(definition, "Type")?; + // 父字段占当前布局前缀,当前层只搜索其后的自有声明。 let inherited = match data.base { Some(base) => self.payload::(base, "Type")?.fields.len(), None => 0, }; + // 命中字段时返回当前声明 owner,让后续注解解析使用正确环境。 if let Some(field) = data.fields[inherited..] .iter() .find(|field| field.name == name) @@ -1349,12 +1438,14 @@ impl ObjectStore { Ok(None) } + /// Store 开启字段检查时校验已声明字段,未声明动态属性不在此限制。 pub(crate) fn check_field_write( &self, object: ObjectRef, name: &str, value: &Value, ) -> Result<(), VmError> { + // 动态未声明字段允许通过;已声明字段按原 owner 的约束验证。 let Some((owner, field)) = self.declared_field(object, name)? else { return Ok(()); }; @@ -1363,12 +1454,15 @@ impl ObjectStore { /// NativeTyped allocators can validate their field suffix before any effects. /// Count is always checked; annotation matching follows the opt-in setting. + /// Record/NativeTyped 构造器在副作用前校验字段后缀;数量始终检查,类型匹配由开关控制。 pub fn check_class_field_arguments( &self, definition: ObjectRef, arguments: &[Value], ) -> Result<(), VmError> { + // 先验证定义/函数载荷,再访问字段表或声明环境。 let data = self.payload::(definition, "Type")?; + // 字段构造数量始终严格相等,错误发生在逐字段类型检查之前。 if arguments.len() != data.fields.len() { return Err(VmError::ArgumentCount { name: data.name.clone(), @@ -1377,8 +1471,11 @@ impl ObjectStore { provided: arguments.len(), }); } + // 可选类型检查开关不改变字段数量规则。 if self.field_type_checks { + // 依照完整继承字段顺序匹配实参,每项还原真实声明层。 for (field, value) in data.fields.iter().zip(arguments) { + // 完整字段表与继承前缀不变量保证声明层存在。 let (owner, field) = self .declared_field_in_type(Some(definition), &field.name)? .expect("class field has a declaring type"); @@ -1388,13 +1485,16 @@ impl ObjectStore { Ok(()) } + /// 声明字段按自身 owner/内建名/声明函数环境解析类型,再执行值匹配。 fn check_field_value( &self, owner: ObjectRef, field: &ClassField, value: &Value, ) -> Result<(), VmError> { + // 验证类型载荷后才读取其基类或能力列表。 let definition = self.payload::(owner, "Type")?; + // 自类名优先、其次内建注解,再查声明函数环境,无环境则未知注解。 let expected = if field.type_name == definition.name { AnnotationType::Nominal(owner) } else if let Some(primitive) = Self::primitive_annotation(&field.type_name) { @@ -1404,6 +1504,7 @@ impl ObjectStore { } else { return Err(VmError::UnknownAnnotation(field.type_name.clone())); }; + // 类型化能力才需要扫描实例存储,最小数量能力没有遮蔽签名要求。 if !self.matches_annotation(value, expected)? { return Err(VmError::TypeMismatch { function: definition.name.clone(), @@ -1415,25 +1516,33 @@ impl ObjectStore { Ok(()) } + /// eval 参数/返回与字段契约解析源码注解,返回借用描述符,不 retain 定义。 pub fn annotation_type( &self, function: ObjectRef, name: &str, receiver: Option, ) -> Result { + // 先验证定义/函数载荷,再访问字段表或声明环境。 let data = self.payload::(function, "function")?; + // 方法 owner 解析需要代码中的源码声明元数据,函数本身只持有代码引用。 let signature = self .payload::(data.code(), "code")? .code() .source_signature(); // Bind a method's own class to the definition that actually owns it, // including inherited methods and old instances after REPL redefinition. + // 只对声明自身 owner 名称且有接收者的场景沿真实类型链查找。 if signature.and_then(|s| s.method_owner.as_deref()) == Some(name) && let Some(receiver) = receiver { + // 从接收者当前名义类型向父类查找实际拥有该源码方法的定义。 let mut current = self.type_of(receiver)?; + // 沿固定基类链逐层读取,不按名称猜测类型。 while let Some(definition) = current { + // 借用继承链类型,要求名称相同且方法表仍持有当前函数。 let payload = self.payload::(definition, "Type")?; + // 同时检查名称和函数身份,避免 REPL 同名重定义误绑定旧实例。 if payload.name == name && payload.methods.values().any(|method| *method == function) { @@ -1442,26 +1551,33 @@ impl ObjectStore { current = payload.base; } } + // 内建注解先于普通环境绑定,避免同名用户对象改变内建语义。 if let Some(primitive) = Self::primitive_annotation(name) { return Ok(primitive); } + // 普通注解查函数全局及内置空间;Option::or 的参数会先求值,因此内置读取不是惰性。 let value = self .dict_get_name(data.globals(), name)? .or(match data.builtins() { Some(builtins) => self.dict_get_name(builtins, name)?, None => None, }); + // 普通名称必须指向 Type 或 Trait 载荷,其他值不能充当类型注解。 if let Some(Value::Object(definition)) = value { + // 查询原对象固有属性身份,而不是只查看转发终点。 let payload = self.object(definition)?.payload(); + // 识别名义类型,描述符只借用该定义身份。 if payload.as_any().is::() { return Ok(AnnotationType::Nominal(definition)); } + // 识别显式能力定义,不以类名字符串替代能力身份。 if payload.as_any().is::() { return Ok(AnnotationType::Capability(definition)); } } Err(VmError::UnknownAnnotation(name.into())) } + /// 调用/字段检查把类型验证读取标记为 validation,普通程序读取仍可另行记录。 pub fn matches_annotation( &self, value: &Value, @@ -1471,14 +1587,17 @@ impl ObjectStore { .with_validation_reads(|| self.matches_annotation_value(value, expected)) } + /// 匹配已解析注解;对象先校验存活,直接类别可同时支持直接值和相应堆载荷。 fn matches_annotation_value( &self, value: &Value, expected: AnnotationType, ) -> Result { + // Any 也不能接受失效句柄,先验证对象存活再匹配类别。 if let Value::Object(object) = value { self.object(*object)?; } + // 直接类别与名义/能力匹配分开;Type/Function 类别不等于任意可调用对象。 Ok(match expected { AnnotationType::Any => true, AnnotationType::Unit => matches!(value, Value::Nil), @@ -1510,7 +1629,9 @@ impl ObjectStore { }, }) } + /// 类型错误诊断格式化预期名称;名义/能力定义仍验证句柄和载荷。 pub fn annotation_type_name(&self, expected: AnnotationType) -> Result { + // 直接类别与名义/能力匹配分开;Type/Function 类别不等于任意可调用对象。 Ok(match expected { AnnotationType::Any => "Object".into(), AnnotationType::Unit => "Unit".into(), @@ -1530,7 +1651,9 @@ impl ObjectStore { .clone(), }) } + /// 类型错误诊断显示实际类型,优先有效名义类型,无类型才回退载荷类别。 pub fn value_type_name(&self, value: &Value) -> Result { + // 直接值显示内建类别;对象优先显示有效名义类型名。 Ok(match value { Value::Nil => "Unit".into(), Value::Bool(_) => "Bool".into(), -- 2.52.0 From 9bf887af1e8f34266d9687f67d711bc53099452d Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:42:18 +0800 Subject: [PATCH 15/62] docs(objects): explain exports and storage arithmetic regressions --- crates/objects/src/lib.rs | 29 ++++++++++++++-- crates/objects/tests/arithmetic.rs | 11 ++++++ crates/objects/tests/store.rs | 54 +++++++++++++++++++++++++++++- 3 files changed, 90 insertions(+), 4 deletions(-) diff --git a/crates/objects/src/lib.rs b/crates/objects/src/lib.rs index 270ab2e..124f6f7 100644 --- a/crates/objects/src/lib.rs +++ b/crates/objects/src/lib.rs @@ -1,6 +1,6 @@ -//! Core Endor objects, values, validated executable data and object storage. -//! This crate has no VM evaluator or external dependencies. Handles, reference -//! accounting and mutable payload replacement remain encapsulated here. +//! Endor 对象、数值、已验证代码和存储的公共 API;compiler/core 和宿主都从这里导入。 +//! 不包含 VM 指令求值器;数值精确运算使用 num_* 依赖,句柄/计数/载荷修改仍封装在本 crate。 +//! 下方示例由文档测试调用:创建拥有根、读写属性,最后 release,不把复制句柄误当新增根。 //! //! ``` //! use objects::{ObjectStore, Value}; @@ -13,41 +13,64 @@ //! # Ok::<(), objects::VmError>(()) //! ``` +// 内部内置载荷:字符串、字典、函数、闭包槽和异常。 mod builtins; +// 内部变更通知结构,消费者据此失效缓存。 mod changes; +// 代码布局和构造校验,不执行代码。 mod code; +// 各层共享错误,避免对象层依赖 VM。 mod error; +// 私有堆:代际身份、根/边计数、只读追踪和回收。 mod heap; +// 指令协议及字节编解码,供编译器和执行器共用。 mod instruction; +// 宿主回调契约、上下文和结果根移交。 mod native; +// 无损数值表示和受预算约束的精确运算。 mod number; +// 直接值、载荷协议及公共对象实体。 mod object; +// 受控对象操作门面,宿主可独立于 VM 使用。 mod store; +// 字典/Cell/函数等内置载荷的 Store 扩展。 mod store_builtins; +// 名义类型、能力、字段注解及子模块中的契约/效果分析。 mod types; +// 向宿主公开内置载荷,分配和修改仍经 Store。 pub use builtins::{ EndorBaseExceptionObject, EndorCellObject, EndorDictObject, EndorFunctionObject, EndorStringObject, EndorTracebackEntry, }; +// 向缓存消费者公开借用变更通知,不提供对象拥有根。 pub use changes::{ChangeBatch, ChangeKey, ObjectChange}; +// 编译器构造与 VM 共享执行数据需要的代码 API。 pub use code::{CodeLayout, EndorCode, EndorCodeObject, SourceSignature}; +// 调用者结构化匹配的统一错误。 pub use error::VmError; +// 只公开句柄和读取观察结果,不导出可变 Heap。 pub use heap::{ObjectRef, ReadAccess, ReadDependency, ReadSet, TrackedReads, WeakObjectRef}; +// 编译器/宿主组装指令,执行器读取解码结果。 pub use instruction::{DecodedInstruction, Instruction, Opcode}; +// 宿主实现原生函数并声明参数、返回值和效果。 pub use native::{ CallArity, CallEffect, CallSignature, CallType, ContextNativeFunction, NativeCallable, NativeContext, NativeResult, }; +// 编译器字面量、VM 算术和宿主显式转换共用数值 API。 pub use number::{ ArithmeticOp, EndorDecimal, EndorInteger, EndorNumber, EndorNumberObject, EndorRational, EndorReal, }; +// 宿主实现载荷及等待协议,保留旧原生函数别名兼容。 pub use object::{ AwaitPoll, CompareOp, EndorNativeCall, EndorNativeFunctionObject, EndorObject, NativeCall, NativeFunction, ObjectHeader, ObjectPayload, Record, Value, }; +// 独立存储的主要入口,VM 也持有同一门面。 pub use store::ObjectStore; +// 宿主注册类型/能力,执行器检查构造和调用契约。 pub use types::{ AnnotationType, ClassField, EndorTraitObject, EndorTypeObject, MethodRequirement, RootTypes, TypeConstructor, TypedMethodRequirement, diff --git a/crates/objects/tests/arithmetic.rs b/crates/objects/tests/arithmetic.rs index 3fe55f9..91db0c3 100644 --- a/crates/objects/tests/arithmetic.rs +++ b/crates/objects/tests/arithmetic.rs @@ -1,20 +1,27 @@ +//! number.rs 的公开 API 回归:比较数学值与存储表示分开,且构造/运算使用不同预算。 use objects::{ArithmeticOp, CompareOp, EndorNumber, EndorReal}; +/// 测试辅助:用 numeric_compare 断言数学相等,不能用 Rust Eq 比较不同表示。 fn equal(a: &EndorNumber, b: &EndorNumber) { assert!(a.numeric_compare(b, CompareOp::Eq).unwrap()); } #[test] +/// 测试框架调用:验证精确跨表示求和、整数溢出提升、有理数等价和大整数混合浮点比较。 fn exact_real_arithmetic_keeps_large_values_and_cross_representation_equivalence() { + // 十进制保留尾零,后续与分数求和应保持精确数学值。 let tenth = EndorNumber::decimal("0.10").unwrap(); + // 分数构造不要求约分,算术结果仍应等价于精确 0.3。 let fifth = EndorNumber::rational("2", "10").unwrap(); equal( &tenth.arithmetic(&fifth, ArithmeticOp::Add).unwrap(), &EndorNumber::decimal("0.3").unwrap(), ); + // 机器最大整数加一必须提升到大整数,不能溢出或丢精度。 let large = EndorNumber::integer("9223372036854775807").unwrap(); equal( &large.arithmetic(&1_i64.into(), ArithmeticOp::Add).unwrap(), &EndorNumber::integer("9223372036854775808").unwrap(), ); + // 最小负整数转换需单独处理其超出正 i64 范围的幅值。 let min = EndorNumber::integer("-9223372036854775808").unwrap(); assert_eq!(min.as_i64(), Some(i64::MIN)); equal( @@ -33,12 +40,15 @@ fn exact_real_arithmetic_keeps_large_values_and_cross_representation_equivalence ); } #[test] +/// 测试框架调用:验证 IEEE 符号零/NaN、零除/非有限结果拒绝,以及巨大指数可存不可运算。 fn ieee_signed_zero_nan_and_operation_limits_are_explicit() { + // 浮点取负翻符号位,+0 应变 -0 而不是规范成整数零。 let negative = EndorNumber::binary_float(0.).negated().unwrap(); assert_eq!( negative.as_real(), Some(&EndorReal::BinaryFloat((-0.0_f64).to_bits())) ); + // 同一 NaN 数学比较也不等于自身,Ne 必须为真。 let nan = EndorNumber::binary_float(f64::NAN); assert!(!nan.numeric_compare(&nan, CompareOp::Eq).unwrap()); assert!(nan.numeric_compare(&nan, CompareOp::Ne).unwrap()); @@ -52,6 +62,7 @@ fn ieee_signed_zero_nan_and_operation_limits_are_explicit() { .arithmetic(&2_i64.into(), ArithmeticOp::Multiply) .is_err() ); + // 极长指数语法合法且可原样显示,但实际运算要触发预算保护。 let huge = EndorNumber::decimal("1e99999999999999999").unwrap(); assert_eq!(huge.to_string(), "1e99999999999999999"); assert!(huge.arithmetic(&1_i64.into(), ArithmeticOp::Add).is_err()); diff --git a/crates/objects/tests/store.rs b/crates/objects/tests/store.rs index 7486d00..7b57fda 100644 --- a/crates/objects/tests/store.rs +++ b/crates/objects/tests/store.rs @@ -1,3 +1,5 @@ +//! 独立 ObjectStore 回归场景,由 Rust 测试框架调用;不需要 VM 执行指令。 +//! 场景覆盖根/边所有权、失败原子性与读取作用域。 use std::sync::Arc; use objects::{ @@ -6,13 +8,21 @@ use objects::{ }; #[test] +/// 验证容器替换保持对象地址、跨堆边被拒绝且失去外部根的互引环可回收。 fn standalone_storage_preserves_identity_validates_edges_and_collects_cycles() { + // 每个测试建立独立存储,避免对象/作用域状态跨场景污染。 let mut store = ObjectStore::new().unwrap(); + // 独立堆用于证明句柄归属不能仅以槽号判断。 let mut other = ObjectStore::new().unwrap(); + // 字典与下面 Cell 各自有一份外部根,Cell 另外持有字典强边。 let dictionary = store.new_dict().unwrap(); + // Cell 初始拥有字典内部边,后续反向写入形成环。 let cell = store.new_cell(Some(Value::Object(dictionary))).unwrap(); + // 异堆对象保持自身外部根,不应被本堆写入接管。 let foreign = other.new_object().unwrap(); + // 保存对象实体地址,验证替换载荷不会移动外层稳定 Box。 let address = store.object(dictionary).unwrap() as *const _; + // 字典键使用直接文本,不产生托管强边。 let key = Value::Text("cell".into()); store @@ -37,10 +47,15 @@ fn standalone_storage_preserves_identity_validates_edges_and_collects_cycles() { } #[test] +/// 验证无效函数构造不分配对象,两个函数共享同一执行数据并通过代码边保留常量。 fn standalone_function_factories_validate_and_own_shared_executable_data() { + // 每个测试建立独立存储,避免对象/作用域状态跨场景污染。 let mut store = ObjectStore::new().unwrap(); + // 字符串最初由测试根持有,随后代码常量也建立内部边。 let constant = store.new_string("kept by code").unwrap(); + // 函数全局空间在两个函数载荷中各持有一条内部边。 let globals = store.new_dict().unwrap(); + // 最小已验证代码只装载常量再返回,不在本测试执行。 let code = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadConst, 0), @@ -50,7 +65,9 @@ fn standalone_function_factories_validate_and_own_shared_executable_data() { CodeLayout::default(), ) .unwrap(); + // 分配代码载荷后常量由其 trace 强边持有。 let code = store.new_code(code).unwrap(); + // 记录失败构造前对象数,断言失败不得泄漏半成品。 let before = store.object_count(); assert_eq!( store.new_function("bad", code, globals, vec![Value::Nil], vec![]), @@ -60,50 +77,66 @@ fn standalone_function_factories_validate_and_own_shared_executable_data() { ); assert_eq!(store.object_count(), before); assert_eq!(store.ref_count(code).unwrap(), 1); + // 第一个函数持有代码和全局字典,不复制代码对象。 let first = store .new_function("first", code, globals, vec![], vec![]) .unwrap(); + // 第二个函数独立持有相同代码/全局边,供释放顺序验证。 let second = store .new_function("second", code, globals, vec![], vec![]) .unwrap(); + // 借用第一个函数记录的代码句柄,不新增根。 let first_code = store .payload::(first, "function") .unwrap() .code(); + // 借用第二个函数代码句柄,与第一函数应指向相同执行数据。 let second_code = store .payload::(second, "function") .unwrap() .code(); + // 取得 Rust Arc 只用于指针共享验证,它不替代托管代码根。 let first_executable = store .payload::(first_code, "code") .unwrap() .shared_code(); + // 第二份 Arc 与第一份应共享同一 EndorCode 分配。 let second_executable = store .payload::(second_code, "code") .unwrap() .shared_code(); assert!(Arc::ptr_eq(&first_executable, &second_executable)); + // 先释放 Rust 共享引用,再测试托管函数/代码边生命周期。 drop(first_executable); drop(second_executable); + // 归还测试拥有根后,常量仍应通过两个函数到代码的图存活。 store.release(constant).unwrap(); store.release(code).unwrap(); store.release(globals).unwrap(); assert_eq!(store.str(constant).unwrap(), "kept by code"); + // 移除一函数仅减一条代码边,另一函数仍保留代码和常量。 store.release(first).unwrap(); assert_eq!(store.ref_count(code).unwrap(), 1); + // 最后一个函数释放后,代码/全局/常量无根链一起回收。 store.release(second).unwrap(); assert_eq!(store.object_count(), 0); } #[test] +/// 验证已有对象受保护、新结果可写、嵌套层会保护外层新对象及退出后恢复权限。 fn read_only_scopes_block_existing_container_and_type_writes_but_allow_fresh_results() { + // 每个测试建立独立存储,避免对象/作用域状态跨场景污染。 let mut store = objects::ObjectStore::new().unwrap(); + // 显式自举两个类型根,清理时需归还它们并收集类型图环。 let roots = store.bootstrap_types().unwrap(); + // 此记录在进入只读前分配,属性和类型附着都应被写屏障保护。 let record = store.new_object().unwrap(); + // 已有字典受同一只读屏障保护,不只保护普通属性。 let dict = store.new_dict().unwrap(); let cell = store .new_cell(Some(objects::Value::Number(1_i64.into()))) .unwrap(); + // 进入时快照已有对象,之后分配的 fresh 不在本层保护集合。 let scope = store.begin_read_only(); assert!( store @@ -129,57 +162,76 @@ fn read_only_scopes_block_existing_container_and_type_writes_but_allow_fresh_res store.cell_get(cell).unwrap(), objects::Value::Number(1_i64.into()) ); + // 在只读调用内分配的新结果可初始化属性。 let fresh = store.new_object().unwrap(); store.set_attr(fresh, "value", objects::Value::Nil).unwrap(); + // Record 不原地赋值,只读引用重绑定可以返回 false 而不修改旧对象。 assert!(!store.assign_from(record, fresh).unwrap()); + // 嵌套层快照/区间只影响自身,结束必须按 LIFO。 let nested = store.begin_read_only(); assert!( store .set_attr(fresh, "value", objects::Value::Bool(true)) .is_err() ); + // 退出内层后 fresh 又不受外层快照保护。 store.end_read_only(nested).unwrap(); store .set_attr(fresh, "value", objects::Value::Bool(true)) .unwrap(); + // 结束外层只读同时取得借用读取集合,Cell 读取应被记录。 let reads = store.end_read_only(scope).unwrap(); assert!(reads.contains(&cell)); store .set_attr(record, "value", objects::Value::Nil) .unwrap(); + // 逐项归还测试拥有根;类型/实例内部边由 Heap 清理,不手动归还。 for object in [record, dict, cell, fresh, roots.object, roots.type_type] { store.release(object).unwrap(); } + // 显式回收自举类型图中的环,最后验证堆为空。 store.collect_cycles(); assert_eq!(store.object_count(), 0); } #[test] +/// 验证子区间重复读取独立记录、观察传播到外层,错误结束顺序不损坏状态。 fn read_tracking_regions_repeat_shared_reads_and_preserve_enclosing_dependencies() { + // 每个测试建立独立存储,避免对象/作用域状态跨场景污染。 let mut store = ObjectStore::new().unwrap(); + // 两个独立对象用于验证读取传播,不建立彼此引用。 let first = store.new_object().unwrap(); + // 第二对象仅在内层首次读取,外层集合也必须包含它。 let second = store.new_object().unwrap(); assert!(store.begin_read_tracking().is_err()); + // 进入时快照已有对象,之后分配的 fresh 不在本层保护集合。 let scope = store.begin_read_only(); + // 外层区间开始后,其内所有读取都要包含在最终结果中。 let outer = store.begin_read_tracking().unwrap(); store.object(first).unwrap(); + // 嵌套层快照/区间只影响自身,结束必须按 LIFO。 let nested = store.begin_read_tracking().unwrap(); store.object(first).unwrap(); store.object(second).unwrap(); + // 内层未结束时不能弹出外层,失败后应仍能正确结束两层。 assert!(store.end_read_tracking(outer).is_err()); assert_eq!( store.end_read_tracking(nested).unwrap(), vec![first, second] ); assert_eq!(store.end_read_tracking(outer).unwrap(), vec![first, second]); + // 新子区间重新读取同一对象,也必须得到自己的观察记录。 let repeated = store.begin_read_tracking().unwrap(); store.object(first).unwrap(); assert_eq!(store.end_read_tracking(repeated).unwrap(), vec![first]); - // Ending the call discards an unfinished region, as on callback failure. + // 模拟回调失败:结束整次调用时丢弃尚未正常结束的区间。 store.begin_read_tracking().unwrap(); assert_eq!(store.end_read_only(scope).unwrap(), vec![first, second]); + // 整个调用结束后未完成区间已丢弃,不能再拿旧令牌结束。 assert!(store.end_read_tracking(1).is_err()); assert!(!store.is_read_only()); + // 移除一函数仅减一条代码边,另一函数仍保留代码和常量。 store.release(first).unwrap(); + // 最后一个函数释放后,代码/全局/常量无根链一起回收。 store.release(second).unwrap(); } -- 2.52.0 From bcf5fb4e967c3933a7a3f8a9646ccf9ee9f928c8 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:44:07 +0800 Subject: [PATCH 16/62] docs(objects): explain attribute forwarding regression scenarios --- crates/objects/tests/attribute_storage.rs | 79 ++++++++++++++++++++++- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/crates/objects/tests/attribute_storage.rs b/crates/objects/tests/attribute_storage.rs index ea6289b..f8047b5 100644 --- a/crates/objects/tests/attribute_storage.rs +++ b/crates/objects/tests/attribute_storage.rs @@ -1,48 +1,73 @@ +//! Rust 测试框架调用的属性存储回归,不执行源码指令。 +//! Proxy 让字段存储与名义身份分开;ReflectiveType 触发类型查询对自身属性的重入。 +//! 断言覆盖托管边生命周期、失败不修改版本、能力方法不得被实例属性遮蔽。 + use objects::{ObjectPayload, ObjectRef, ObjectStore, TypeConstructor, Value, VmError}; #[derive(Debug)] +/// 一个拥有后端存储引用的门面,type_target 保持默认 None。 struct Proxy(ObjectRef); impl ObjectPayload for Proxy { + /// Store 下转型测试载荷,不进行语言类型判定。 fn as_any(&self) -> &dyn std::any::Any { self } + /// 测试错误/诊断使用的类别标签。 fn kind_name(&self) -> &'static str { "proxy" } + /// Heap 分配/替换时记录一条拥有边,确保被代理存储或自引用 Cell 存活。 fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { visit(self.0); } + /// Store::attribute_path 追踪实际字段存储,不改变代理对象头部类型。 fn attribute_target(&self, _: &ObjectStore) -> Result, VmError> { Ok(Some(self.0)) } } #[test] +/// 验证代理改指新存储后,名义类型不变、旧值释放、读写删除转到新终点。 fn storage_retargeting_preserves_type_identity_and_releases_old_state() { + // 建立独立堆隔离场景,所有分配根在测试末尾明确归还。 let mut store = ObjectStore::new().unwrap(); + // 自举 Object/Type,各有一份外部根;其图内自环需最终 collect_cycles。 let roots = store.bootstrap_types().unwrap(); + // 给代理/反射实例附着真实名义类型,字段转发不应替换它。 let class = store .new_type("Proxy", roots.object, TypeConstructor::Record) .unwrap(); + // 普通 Record 作为无类型后端存储,只有代理持有时也须存活。 let storage = store.new_object().unwrap(); + // 旧属性值先由测试根持有,之后转由字段强边持有。 let old_value = store.new_object().unwrap(); + // 弱句柄不持有旧值,替换存储后可观测其真正释放。 let weak_value = store.downgrade(old_value).unwrap(); store .set_attr(storage, "value", Value::Object(old_value)) .unwrap(); + // 归还外部根,留下 storage.value 一条强边。 store.release(old_value).unwrap(); + // Proxy 的 trace 持有后端,字段实际写入后端但类型留在 Proxy 头部。 let proxy = store.allocate(Proxy(storage)).unwrap(); store.attach_type(proxy, class).unwrap(); + // 归还后端外部根后仍由 Proxy 的载荷边维持存活。 store.release(storage).unwrap(); + // 确认路径同时包含门面和终点,不只返回头部类型对象。 assert_eq!(store.attribute_path(proxy).unwrap(), vec![proxy, storage]); + // 保留 type 属性查询代理自身名义类型,不跟随后端身份变化。 assert_eq!(store.get_attr(proxy, "type").unwrap(), Value::Object(class)); assert_eq!( store.get_attr(proxy, "value").unwrap(), Value::Object(old_value) ); + // 新后端先由根持有,替换成功后 Proxy 自己持有一条边。 let next = store.new_object().unwrap(); + // 受控替换重新登记边,旧后端及其唯一持有的属性值应被级联释放。 store.replace_payload(proxy, Proxy(next)).unwrap(); + // 归还新后端根,后续访问证明其仍由 Proxy 强边保留。 store.release(next).unwrap(); + // 旧值没有残余根,弱升级必须返回 None。 assert!(store.upgrade(weak_value).unwrap().is_none()); store .set_attr(proxy, "value", Value::Number(9_i64.into())) @@ -51,48 +76,67 @@ fn storage_retargeting_preserves_type_identity_and_releases_old_state() { store.get_attr(next, "value").unwrap(), Value::Number(9_i64.into()) ); + // 通过代理删除应作用于新后端,不只是门面自己的属性表。 store.del_attr(proxy, "value").unwrap(); assert!(store.get_attr(next, "value").is_err()); + // 逐项释放测试拥有根,不手动 release 内部边,避免重复归还。 for object in [proxy, class, roots.object, roots.type_type] { store.release(object).unwrap(); } + // 回收类型自举环及其他已无外部根的环,最终堆应为空。 store.collect_cycles(); assert_eq!(store.object_count(), 0); } #[test] +/// 验证属性转发环和异堆身份被拒绝,失败不产生新的变更版本。 fn cyclic_and_foreign_attribute_storage_fail_without_mutating_fields() { + // 建立独立堆隔离场景,所有分配根在测试末尾明确归还。 let mut store = ObjectStore::new().unwrap(); + // 普通 Record 作为无类型后端存储,只有代理持有时也须存活。 let storage = store.new_object().unwrap(); + // Proxy 的 trace 持有后端,字段实际写入后端但类型留在 Proxy 头部。 let proxy = store.allocate(Proxy(storage)).unwrap(); + // 注入合法托管自环但非法属性转发环;属性路径必须检测,不无限循环。 store.replace_payload(proxy, Proxy(proxy)).unwrap(); + // 记录失败读写前版本,路径检查失败不得修改对象。 let version = store.change_version(proxy).unwrap(); assert!(store.get_attr(proxy, "field").is_err()); assert!(store.set_attr(proxy, "field", Value::Nil).is_err()); assert_eq!(store.change_version(proxy).unwrap(), version); + // 异堆即使槽号相同也必须报 ForeignHandle,不当成普通缺字段。 let other = ObjectStore::new().unwrap(); assert_eq!(other.attribute_path(proxy), Err(VmError::ForeignHandle)); + // 归还后端外部根后仍由 Proxy 的载荷边维持存活。 store.release(storage).unwrap(); store.release(proxy).unwrap(); + // 外部根释放后只有 Proxy 自环残留,显式回收恰好一个对象。 assert_eq!(store.collect_cycles(), 1); } #[derive(Debug)] +/// 拥有一个 Cell,type_target 读 Cell 指回自己,测试递归属性查询。 struct ReflectiveType(ObjectRef); impl ObjectPayload for ReflectiveType { + /// Store 下转型测试载荷,不进行语言类型判定。 fn as_any(&self) -> &dyn std::any::Any { self } + /// 测试错误/诊断使用的类别标签。 fn kind_name(&self) -> &'static str { "reflective-type" } + /// 反射对象自行存字段,允许 selector 查询从公共属性表读取。 fn allows_attributes(&self) -> bool { true } + /// Heap 分配/替换时记录一条拥有边,确保被代理存储或自引用 Cell 存活。 fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { visit(self.0); } + /// Store::type_of 调用时通过 Cell 找自己并读 selector,专门制造属性检查重入。 fn type_target(&self, store: &ObjectStore) -> Result, VmError> { + // Cell 初始 Nil 时不重入,安装自引用后读自身 selector 触发保护守卫路径。 if let Value::Object(receiver) = store.cell_get(self.0)? { store.get_attr(receiver, "selector")?; } @@ -101,8 +145,10 @@ impl ObjectPayload for ReflectiveType { } #[derive(Debug)] +/// 无捕获且不修改存储的能力方法测试实现;完整签名供声明/调用门禁读取。 struct TypedCheck; impl objects::NativeCallable for TypedCheck { + /// 为 check 声明显式 self、Number 返回和 ReadOnly 效果,供能力契约核对。 fn signature(&self) -> Option { Some(objects::CallSignature::exact( vec![objects::AnnotationType::Any], @@ -110,6 +156,7 @@ impl objects::NativeCallable for TypedCheck { objects::CallEffect::ReadOnly, )) } + /// 原生分派返回直接整数,无对象结果需要额外保留;本文件主要验证契约门禁。 fn invoke( &self, _: &mut objects::NativeContext<'_>, @@ -120,12 +167,17 @@ impl objects::NativeCallable for TypedCheck { } #[test] +/// 验证能力声明/调用各入口均拒绝同名字段遮蔽,且递归守卫失败后正确清理。 fn typed_method_storage_cannot_diverge_from_class_dispatch() { + // 建立独立堆隔离场景,所有分配根在测试末尾明确归还。 let mut store = ObjectStore::new().unwrap(); + // 自举 Object/Type,各有一份外部根;其图内自环需最终 collect_cycles。 let roots = store.bootstrap_types().unwrap(); + // 给代理/反射实例附着真实名义类型,字段转发不应替换它。 let class = store .new_type("Checked", roots.object, TypeConstructor::Record) .unwrap(); + // 上下文原生函数发布完整签名;类方法表还会单独拥有它的强边。 let function = store .allocate(objects::ContextNativeFunction::new( "check", @@ -134,6 +186,7 @@ fn typed_method_storage_cannot_diverge_from_class_dispatch() { )) .unwrap(); store.define_method(class, "check", function).unwrap(); + // 能力要求与原生方法完整契约一致,包含显式 self 参数。 let capability = store .new_typed_trait( "Checked", @@ -147,19 +200,28 @@ fn typed_method_storage_cannot_diverge_from_class_dispatch() { }], ) .unwrap(); + // 普通 Record 作为无类型后端存储,只有代理持有时也须存活。 let storage = store.new_object().unwrap(); + // Proxy 的 trace 持有后端,字段实际写入后端但类型留在 Proxy 头部。 let proxy = store.allocate(Proxy(storage)).unwrap(); store.attach_type(proxy, class).unwrap(); + // 直接写无类型后端可以造出对代理而言不符合能力的实例,后续门禁应拒绝。 store.set_attr(storage, "check", Value::Nil).unwrap(); + // 能力声明失败应保持定义版本和声明集合不变。 let version = store.change_version(class).unwrap(); assert!(matches!( + // 存储冲突消除后能力声明才允许提交。 store.implement_trait(class, capability), Err(VmError::TraitViolation { .. }) )); assert_eq!(store.change_version(class).unwrap(), version); + // 失败声明不能留下一半能力关系。 assert!(!store.implements_trait(class, capability).unwrap()); + // 删除遮蔽字段恢复合法存储,后续能力声明/查询应重新成功。 store.del_attr(storage, "check").unwrap(); + // 存储冲突消除后能力声明才允许提交。 store.implement_trait(class, capability).unwrap(); + // 代理受控写入在真正触及后端前失败,后端版本不得增加。 let version = store.change_version(storage).unwrap(); assert!(matches!( store.set_attr(proxy, "check", Value::Nil), @@ -167,8 +229,8 @@ fn typed_method_storage_cannot_diverge_from_class_dispatch() { )); assert_eq!(store.change_version(storage).unwrap(), version); assert!(store.supports(proxy, capability).unwrap()); - // A trusted host can access the untyped backing record directly. Every - // language/host dispatch gate must detect the resulting invalid instance. + // 可信宿主能直接写无类型后端;supports/get_attr/bind_attr/调用检查/直接存储检查都必须发现失效实例。 + // 直接写无类型后端可以造出对代理而言不符合能力的实例,后续门禁应拒绝。 store.set_attr(storage, "check", Value::Nil).unwrap(); assert!(matches!( store.supports(proxy, capability), @@ -190,13 +252,17 @@ fn typed_method_storage_cannot_diverge_from_class_dispatch() { store.check_typed_instance_storage(proxy), Err(VmError::TraitViolation { .. }) )); + // 另建已有冲突字段的记录,验证 attach_type 也守住能力存储约束。 let candidate = store.new_object().unwrap(); + // 先放同名字段再附着类型,不能绕过“创建后写入”的校验。 store.set_attr(candidate, "check", Value::Nil).unwrap(); assert!(matches!( store.attach_type(candidate, class), Err(VmError::TraitViolation { .. }) )); + // 附着失败不能留下部分头部类型关系。 assert_eq!(store.type_of(candidate).unwrap(), None); + // 删除遮蔽字段恢复合法存储,后续能力声明/查询应重新成功。 store.del_attr(storage, "check").unwrap(); assert!(store.supports(proxy, capability).unwrap()); assert_eq!( @@ -206,7 +272,9 @@ fn typed_method_storage_cannot_diverge_from_class_dispatch() { store .check_method_contract_call(function, proxy, false) .unwrap(); + // 用托管 Cell 保存反射对象,初始 Nil 避免初始化时就递归查询未设字段。 let self_cell = store.new_cell(Some(Value::Nil)).unwrap(); + // 载荷持有 Cell,Cell 稍后反向持有对象,形成可回收的托管环。 let reflective = store.allocate(ReflectiveType(self_cell)).unwrap(); store .set_attr(reflective, "selector", Value::Bool(true)) @@ -214,6 +282,7 @@ fn typed_method_storage_cannot_diverge_from_class_dispatch() { store .cell_set(self_cell, Value::Object(reflective)) .unwrap(); + // 附着能力类型期间查询 selector 会重入检查,守卫应避免无限递归。 store.attach_type(reflective, class).unwrap(); assert_eq!( store.get_attr(reflective, "selector").unwrap(), @@ -224,14 +293,17 @@ fn typed_method_storage_cannot_diverge_from_class_dispatch() { store.set_attr(reflective, "check", Value::Nil), Err(VmError::TraitViolation { .. }) )); - // A failed guarded check must not suppress the next one. + // 首次失败必须通过 RAII 移除标记;第二次同样的非法写入仍应失败,不能被误当成递归跳过。 assert!(matches!( store.set_attr(reflective, "check", Value::Nil), Err(VmError::TraitViolation { .. }) )); + // 归还自引用图外部根;环内边仍在,统一由最终回收处理。 store.release(reflective).unwrap(); + // Cell 的外部根也归还,不能仅释放反射门面而留下测试根。 store.release(self_cell).unwrap(); + // 逐项释放测试拥有根,不手动 release 内部边,避免重复归还。 for object in [ candidate, proxy, @@ -244,6 +316,7 @@ fn typed_method_storage_cannot_diverge_from_class_dispatch() { ] { store.release(object).unwrap(); } + // 回收类型自举环及其他已无外部根的环,最终堆应为空。 store.collect_cycles(); assert_eq!(store.object_count(), 0); } -- 2.52.0 From 9cfae6ab7071035953182768d270b181c46652c2 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:45:54 +0800 Subject: [PATCH 17/62] docs(core): explain frame roots and suspended execution states --- crates/core/src/frame.rs | 75 ++++++++++++++++++++++++++++++++++++++++ crates/core/src/lib.rs | 8 +++++ 2 files changed, 83 insertions(+) diff --git a/crates/core/src/frame.rs b/crates/core/src/frame.rs index 3a84503..8f40c23 100644 --- a/crates/core/src/frame.rs +++ b/crates/core/src/frame.rs @@ -1,3 +1,7 @@ +//! eval 创建、推进并清理执行状态的数据结构;这里不执行指令,也不自动释放复制句柄。 +//! 活跃帧用外部执行根保留对象;停放到 Coroutine 载荷后由其 trace 边保留同一组身份。 +//! Arc 只共享 Rust 帧/代码内存,根与内部边的转换由 eval 显式完成。 + use std::sync::Arc; use crate::{EndorCode, ObjectRef, Value}; @@ -6,16 +10,21 @@ use crate::{EndorCode, ObjectRef, Value}; /// Each Value::Object in a live frame owns one execution root. #[derive(Debug, Clone)] pub(crate) enum StackEntry { + /// 调用协议占位,不能作为语言 Value 逃逸。 Null, + /// 活跃帧中对象值拥有执行根,Rust Clone 不替调用者增加计数。 Value(Value), } #[derive(Debug)] pub(crate) struct NativeReturn { + /// 委托返回约束的诊断名,不代表按名字查回原函数。 pub name: String, + /// 需要保持的返回类型约束;句柄 clone 本身不 retain。 pub expected: crate::CallType, } impl NativeReturn { + /// eval 在普通/委托返回时对结果执行原生约束,错误保留诊断名和 return 位置。 pub fn check(&self, store: &objects::ObjectStore, value: &Value) -> Result<(), crate::VmError> { self.expected .check(store, &self.name, "return".into(), value) @@ -26,45 +35,66 @@ impl NativeReturn { /// EndorObject header; introspection objects can be added independently later. #[derive(Debug)] pub struct EndorFrame { + /// 活跃帧拥有的函数根;函数强边使 code/globals/builtins 保持可达。 pub(crate) function: ObjectRef, + /// 已解析源码返回注解;含定义句柄时另持一份执行根。 pub(crate) return_type: Option, + /// 原生委托链带来的返回检查,每项定义根由 eval 显式保留/释放。 pub(crate) native_returns: Vec, /// Index of the caller in the execution's frame vector, never a pointer. pub(crate) previous: Option, + /// 借用函数指向的托管代码身份,不额外作为独立执行根。 pub(crate) code: ObjectRef, + /// 共享不可变执行数据;不替代函数/代码的托管引用保活。 pub(crate) executable: Arc, + /// 借用函数拥有的全局字典。 pub(crate) globals: ObjectRef, + /// 借用函数创建时捕获的内置字典,可为 None。 pub(crate) builtins: Option, + /// 本帧诊断名,回溯构造时复制。 pub(crate) name: String, + /// 下一条/恢复时将执行的代码单元偏移。 pub(crate) instruction_offset: usize, + /// 当前或最近执行的指令起点,错误回溯使用。 pub(crate) current_offset: usize, + /// 当前是否因 await Pending 停放;不是普通切片预算耗尽。 pub(crate) waiting: bool, + /// 局部和自由变量槽;None 为未绑定,每个对象值在活跃帧中拥有一根。 pub(crate) localsplus: Vec>, + /// 求值栈对象项各拥有一根,Null 标记不拥有对象。 pub(crate) stack: Vec, } impl EndorFrame { + /// 宿主只读查询调用者帧索引,None 表示此帧无调用者。 pub fn previous(&self) -> Option { self.previous } + /// 宿主检查当前函数的借用身份,不新增根。 pub fn function(&self) -> ObjectRef { self.function } + /// 宿主查询托管代码借用身份,不复制执行数据。 pub fn code(&self) -> ObjectRef { self.code } + /// 宿主查询全局字典借用身份。 pub fn globals(&self) -> ObjectRef { self.globals } + /// 宿主查询已捕获内置空间借用身份,未配置为 None。 pub fn builtins(&self) -> Option { self.builtins } + /// 宿主查看下一次执行位置,单位为代码单元。 pub fn instruction_offset(&self) -> usize { self.instruction_offset } + /// 宿主借用槽快照视图,克隆对象 Value 不自动变成拥有根。 pub fn localsplus(&self) -> &[Option] { &self.localsplus } + /// 宿主/测试观察栈项数量,包含协议 Null。 pub fn stack_depth(&self) -> usize { self.stack.len() } @@ -72,19 +102,26 @@ impl EndorFrame { #[derive(Debug, Clone, PartialEq, Eq)] pub struct ExecutionLocation { + /// 回溯的函数诊断名称。 pub function: String, + /// 回溯的源码文件标识,不访问磁盘。 pub filename: String, + /// 代码声明起始行,不是精确逐指令行表。 pub first_line: usize, + /// 故障指令的代码单元偏移,不是源码字符位置。 pub instruction_offset: usize, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ExecutionLimits { + /// 嵌套源码帧硬上限,零可阻止源码帧执行。 pub max_frames: usize, + /// 累计执行/调用准备预算,跨切片继续累计。 pub max_instructions: usize, } impl Default for ExecutionLimits { + /// 未指定预算时默认最多 1000 个源码帧,指令限制为 usize::MAX。 fn default() -> Self { Self { max_frames: 1000, @@ -97,22 +134,33 @@ impl Default for ExecutionLimits { /// owned value plus borrowed read dependencies. Instruction counts accumulate. #[derive(Debug)] pub enum ExecutionProgress { + /// 软切片耗尽,状态仍保存于 VM/托管协程中,不能重放此前代码。 Pending { + /// 从执行开始累计的计数,不是当前切片的局部数量。 instructions: usize, }, + /// 已完成;对象结果移交调用者一份拥有根,须通过 VM 归还。 Complete { + /// 完成时移出的拥有结果;直接标量没有托管根。 value: Value, + /// 借用读取身份,不保留被观察图。 reads: Vec, + /// 精确借用观察,含名称值快照也不建立拥有根。 dependencies: Vec, + /// 从执行开始累计的计数,不是当前切片的局部数量。 instructions: usize, }, } #[derive(Debug)] pub(crate) struct SuspendedCall { + /// 暂停的只读调用帧,执行根仍由 VM 持有。 pub frames: Vec, + /// 跨 Pending 保持活跃的 Heap 只读作用域令牌。 pub scope: usize, + /// 创建执行时固定的累计硬限制。 pub limits: ExecutionLimits, + /// 已消耗的累计指令/调用准备计数,不随每个 quantum 清零。 pub executed: usize, } @@ -120,44 +168,64 @@ pub(crate) struct SuspendedCall { /// not keep a VM-wide heap scope active between quanta. #[derive(Debug)] pub enum CoroutineProgress { + /// await 尚未就绪,等待对象句柄借自仍停放的协程帧。 Waiting { + /// 待观察对象的借用身份,协程仍须保持有根才能保证它存活。 awaitable: ObjectRef, + /// 从执行开始累计的计数,不是当前切片的局部数量。 instructions: usize, }, + /// 软切片耗尽,状态仍保存于 VM/托管协程中,不能重放此前代码。 Pending { + /// 从执行开始累计的计数,不是当前切片的局部数量。 instructions: usize, }, + /// 已完成;对象结果移交调用者一份拥有根,须通过 VM 归还。 Complete { + /// 完成时移出的拥有结果;直接标量没有托管根。 value: Value, + /// 从执行开始累计的计数,不是当前切片的局部数量。 instructions: usize, }, } #[derive(Debug)] pub(crate) enum CoroutineState { + /// 停放的帧快照,执行根已转为协程载荷强边。 Frames(Arc>), + /// 准备阶段原生调用已返回结果,作为载荷边等待 resume 移交。 Ready(Value), + /// eval 正推进帧的重入保护标记,执行根暂由 eval 持有。 Running, + /// 已完成或取消,不再持有帧/结果图。 Finished, } #[derive(Debug)] pub(crate) struct Coroutine { + /// 托管协程的当前状态,eval 用载荷替换提交状态变化。 pub state: CoroutineState, + /// 创建执行时固定的累计硬限制。 pub limits: ExecutionLimits, + /// 已消耗的累计指令/调用准备计数,不随每个 quantum 清零。 pub executed: usize, } impl EndorFrame { /// Exactly the references held as execution roots while this frame runs. /// code/globals/builtins remain reachable through its owning function. + /// eval 停放/恢复协程时枚举根身份,也是 Coroutine 载荷 trace 的内部边来源。 pub(crate) fn trace_roots(&self, visit: &mut dyn FnMut(ObjectRef)) { visit(self.function); + // 返回注解中只有名义/能力类别引用定义,这些根也要停放到协程图。 if let Some(reference) = self.return_type.and_then(|kind| kind.reference()) { visit(reference); } + // 每个委托约束都独立持有定义根,重复引用不能去重。 for check in &self.native_returns { + // 参数联合等元数据可能包含多个定义,枚举完整引用集合。 for reference in check.expected.references() { visit(reference); } } + // 局部空槽和 Null 不持有根,其他对象槽逐项输出身份。 for value in self .localsplus .iter() @@ -167,6 +235,7 @@ impl EndorFrame { StackEntry::Null => None, })) { + // 直接数值/文本等不形成托管对象边。 if let Value::Object(reference) = value { visit(*reference); } @@ -174,18 +243,24 @@ impl EndorFrame { } } impl crate::ObjectPayload for Coroutine { + /// VM 从托管对象载荷下转型为 Coroutine 状态。 fn as_any(&self) -> &dyn std::any::Any { self } + /// 协程协议错误与诊断的类别名称。 fn kind_name(&self) -> &'static str { "coroutine" } + /// 协程载荷无旧式原地赋值,普通引用重绑定不用改其状态。 fn reference_assignment_only(&self) -> bool { true } + /// Heap 分配/替换协程载荷时记录停放状态拥有边,运行/完成状态不保留帧图。 fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { + // 停放帧与准备结果拥有图,Running/Finished 不重复登记执行器当前持有的根。 match &self.state { CoroutineState::Frames(frames) => { + // 帧根枚举与 eval 恢复时的保护清单保持一致。 for frame in frames.iter() { frame.trace_roots(visit); } diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index cc744b5..dd07f48 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,3 +1,5 @@ +//! compiler 和宿主导入的 VM 公共入口;对象表示与堆操作从 objects 重导出,避免维护第二套类型。 +//! 两段示例分别展示显式根生命周期和直接执行已验证指令,文档测试使用同一公开 API。 //! Embeddable wordcode VM backed by the independent objects crate. No parser or //! Python language type system; wordcode is not CPython bytecode compatible. //! @@ -38,14 +40,19 @@ //! # Ok::<(), endor_core::VmError>(()) //! ``` +// 指令求值、调用准备、暂停/协程与根清理的内部实现。 mod eval; +// 执行帧、预算、回溯与进度状态结构;不定义另一套对象堆。 mod frame; +// VM 持有 Store 与执行状态,并提供宿主访问门面。 mod vm; +// 公开执行观察和进度类型,内部 Coroutine 载荷状态不导出。 pub use frame::{ CoroutineProgress, EndorFrame, ExecutionLimits, ExecutionLocation, ExecutionProgress, }; // Preserve existing embedding imports; objects owns these definitions. +// 独立存储入口来自 objects,宿主可不创建求值器就使用。 pub use objects::ObjectStore; pub use objects::{ AnnotationType, AwaitPoll, ClassField, EndorTraitObject, EndorTypeObject, MethodRequirement, @@ -61,4 +68,5 @@ pub use objects::{ NativeResult, ObjectChange, ObjectHeader, ObjectPayload, ObjectRef, Opcode, ReadAccess, ReadDependency, ReadSet, Record, SourceSignature, TrackedReads, Value, VmError, WeakObjectRef, }; +// 嵌入方启动/恢复调用和访问对象的主要入口。 pub use vm::Vm; -- 2.52.0 From 48629ab55b0d8d21c73d60abc8a9cf9fbd724c77 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:47:28 +0800 Subject: [PATCH 18/62] docs(core): explain VM facade ownership and store delegation --- crates/core/src/vm.rs | 69 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/crates/core/src/vm.rs b/crates/core/src/vm.rs index 6f67be4..56bf63a 100644 --- a/crates/core/src/vm.rs +++ b/crates/core/src/vm.rs @@ -1,3 +1,7 @@ +//! 嵌入宿主及 eval 共用的 VM 门面;本文件多数方法直接委托 ObjectStore,不重复实现堆规则。 +//! 指令执行/调用/暂停在 eval,状态类型在 frame;这里仅持有状态并暴露受控对象 API。 +//! new_*/allocate/成功 upgrade 返回拥有根;get_attr/cell_get/dict_get 等读取仍为借用句柄。 + use objects::ObjectStore; use crate::{ @@ -9,36 +13,47 @@ use crate::{ /// reference accounting and builtin operations live in the objects crate. #[derive(Debug)] pub struct Vm { + // 唯一对象图与受控堆门面,对象规则不在 VM 重复实现。 pub(crate) objects: ObjectStore, + // 最近执行失败的回溯快照,eval 启动新调用时清空。 pub(crate) last_traceback: Vec, + // 源码参数/返回检查开关,与 Store 字段检查一起配置,默认关闭。 pub(crate) source_type_checks: bool, + // VM 最多保存一个只读续执行及其活跃只读作用域;可变协程另在托管载荷中。 pub(crate) suspended_call: Option, } impl Vm { + /// 宿主开启源码参数/返回及 Store 字段检查;只读调用期间不能改变配置。 pub fn set_source_type_checks(&mut self, enabled: bool) -> Result<(), VmError> { + // 配置会影响后续语义,不能由只读调用改变,即使写入相同值也拒绝。 if self.objects.is_read_only() { return Err(VmError::UnsupportedOperation( "configure source type checks during read-only call", )); } + // 先让 Store 接受字段配置,成功后才更新 VM 的源码检查开关。 self.objects.set_field_type_checks(enabled)?; self.source_type_checks = enabled; Ok(()) } /// Known whole-call effect; unproved source/delegated calls return None. + /// 宿主调度/契约查询当前整次调用效果;无法证明或动态委托返回 None,不代表确定写入。 pub fn call_effect(&self, function: ObjectRef) -> Result, VmError> { self.objects.call_effect(function) } + /// 宿主参数检查委托 Store,统一源码/原生数量区间并扣除已绑定接收者。 pub fn call_arity(&self, function: ObjectRef) -> Result, VmError> { self.objects.call_arity(function) } + /// 宿主初始化 Function 名义类型配置,Store 保存弱引用用于后续函数分配。 pub fn set_function_type(&mut self, definition: ObjectRef) -> Result<(), VmError> { self.objects.set_function_type(definition) } + /// 宿主注册最小方法数量能力,返回一份外部拥有根,要求数量不含 self。 pub fn new_trait( &mut self, name: impl Into, @@ -46,6 +61,7 @@ impl Vm { ) -> Result { self.objects.new_trait(name, requirements) } + /// 宿主注册含 self 的完整参数/返回/效果能力,成功返回一份拥有根。 pub fn new_typed_trait( &mut self, name: impl Into, @@ -53,6 +69,7 @@ impl Vm { ) -> Result { self.objects.new_typed_trait(name, requirements) } + /// 宿主给类型显式声明能力,Store 先校验后代方法和已有实例再提交。 pub fn implement_trait( &mut self, definition: ObjectRef, @@ -60,6 +77,7 @@ impl Vm { ) -> Result<(), VmError> { self.objects.implement_trait(definition, capability) } + /// 宿主查询类型显式/继承声明,命中还会重验当前契约,不做结构猜测。 pub fn implements_trait( &self, definition: ObjectRef, @@ -67,10 +85,12 @@ impl Vm { ) -> Result { self.objects.implements_trait(definition, capability) } + /// 宿主/UI bridge 查询值级能力,同时核对有效类型及实例存储遮蔽。 pub fn supports(&self, object: ObjectRef, capability: ObjectRef) -> Result { self.objects.supports(object, capability) } + /// 宿主注册或替换方法,Store 校验能力/覆盖并维护方法强边,保留类型身份。 pub fn define_method( &mut self, type_object: ObjectRef, @@ -82,15 +102,18 @@ impl Vm { /// Owned result; release_value when finished. Unlike get_attr, methods bind /// their receiver and retain it until the bound method itself is released. + /// eval LoadAttr/宿主取得拥有结果;方法包装持有原接收者,结果用 release_value 归还。 pub fn bind_attr(&mut self, object: ObjectRef, name: &str) -> Result { self.objects.bind_attr(object, name) } + /// 宿主显式自举 Object 与 Type,返回两份根;Vm::new 不隐式调用它。 pub fn bootstrap_types(&mut self) -> Result { self.objects.bootstrap_types() } /// Host registration uses the same validated class builder as source code. + /// 宿主使用与源码相同的类构造校验,继承字段在前,失败不交付半成品。 pub fn new_class( &mut self, name: impl Into, @@ -101,6 +124,7 @@ impl Vm { self.objects.new_class(name, base, fields, methods) } + /// 宿主构造名义派生类型及实例分配策略,返回一份拥有根。 pub fn new_type( &mut self, name: impl Into, @@ -110,6 +134,7 @@ impl Vm { self.objects.new_type(name, base, constructor) } + /// 宿主/构造路径附着不可替换的名义类型,能力和只读屏障由 Store/Heap 检查。 pub fn attach_type( &mut self, object: ObjectRef, @@ -118,27 +143,33 @@ impl Vm { self.objects.attach_type(object, type_object) } + /// 宿主/执行器查询有效名义类型,遵循 type_target 转发;返回句柄借用,无类型为 None。 pub fn type_of(&self, object: ObjectRef) -> Result, VmError> { self.objects.type_of(object) } + /// 宿主类型检查沿固定基类链按身份判定,包含自反关系。 pub fn is_subtype(&self, candidate: ObjectRef, expected: ObjectRef) -> Result { self.objects.is_subtype(candidate, expected) } + /// 宿主值类型检查以有效类型判定,无名义类型对象为 false。 pub fn is_instance(&self, object: ObjectRef, expected: ObjectRef) -> Result { self.objects.is_instance(object, expected) } /// Read-only access for embedding protocols sharing the same object graph. + /// 宿主共享同一对象图的只读协议入口,不提供绕过写屏障的可变存储。 pub fn object_store(&self) -> &ObjectStore { &self.objects } + /// 宿主缓存弱身份,不新增计数、不消耗当前拥有根;句柄仍须先有效。 pub fn downgrade(&self, reference: ObjectRef) -> Result { self.objects.downgrade(reference) } + /// 宿主尝试把弱身份转为一份新拥有根;过期返回 None,异堆仍报错。 pub fn upgrade( &mut self, reference: crate::WeakObjectRef, @@ -146,14 +177,17 @@ impl Vm { self.objects.upgrade(reference) } + /// 宿主渲染/模型缓存取走累计变更与溢出标志,通知本身不保留源对象。 pub fn take_changes(&mut self) -> crate::ChangeBatch { self.objects.take_changes() } + /// 缓存观察当前对象版本;读取会经过 Store 的依赖收集入口。 pub fn change_version(&self, reference: ObjectRef) -> Result { self.objects.change_version(reference) } + /// 宿主受控提交载荷及 trace 边快照,稳定对象身份,先校验/保留新边再释放旧边。 pub fn replace_payload( &mut self, reference: ObjectRef, @@ -162,7 +196,9 @@ impl Vm { self.objects.replace_payload(reference, payload) } + /// 嵌入方创建空 Store 和空执行状态;不注册类型、函数或语言全局空间。 pub fn new() -> Result { + // 空状态与 Store::new 保持一致,执行时按需建立全局空间与帧。 Ok(Self { objects: ObjectStore::new()?, last_traceback: Vec::new(), @@ -171,56 +207,69 @@ impl Vm { }) } + /// 宿主/测试查询当前存活堆对象数,不包括直接 Value 标量。 pub fn object_count(&self) -> usize { self.objects.object_count() } /// Returns one external strong reference. Copies of the returned handle /// are borrowed; use retain/release to manage additional owned references. + /// 宿主/内部构造载荷的统一门面,Store 负责内置校验、强边计数与函数类型附着。 pub fn allocate(&mut self, payload: impl ObjectPayload) -> Result { self.objects.allocate(payload) } + /// 宿主/原生构造器创建可附动态字段的 Record,返回一份根,类型可另附着。 pub fn new_object(&mut self) -> Result { self.objects.new_object() } + /// eval/宿主借用完整对象,验证句柄并收集整对象读取,不新增根。 pub fn object(&self, reference: ObjectRef) -> Result<&EndorObject, VmError> { self.objects.object(reference) } + /// 宿主/执行槽取得额外外部根,复制 ObjectRef 本身不会自动执行此操作。 pub fn retain(&mut self, reference: ObjectRef) -> Result<(), VmError> { self.objects.retain(reference) } + /// 宿主/执行槽归还一份拥有根,零强计数时级联释放;不能归还仅借用的内部边。 pub fn release(&mut self, reference: ObjectRef) -> Result<(), VmError> { self.objects.release(reference) } + /// 测试/诊断读总强计数,包含外部根和堆内边,不是仅宿主引用数。 pub fn ref_count(&self, reference: ObjectRef) -> Result { self.objects.ref_count(reference) } + /// 宿主/执行器先验证两端再比较完整身份,不调用数值或载荷相等协议。 pub fn is_same(&self, left: ObjectRef, right: ObjectRef) -> Result { self.objects.is_same(left, right) } + /// 宿主/错误显示委托载荷诊断表示,不运行源码 repr 方法。 pub fn repr(&self, reference: ObjectRef) -> Result { self.objects.repr(reference) } + /// 宿主显示文本协议,默认载荷可回退 repr,不自动转为语言调用。 pub fn str(&self, reference: ObjectRef) -> Result { self.objects.str(reference) } + /// eval 条件分支/宿主走载荷真值协议,协议失败保持错误。 pub fn is_true(&self, reference: ObjectRef) -> Result { self.objects.is_true(reference) } + /// 字典键/宿主走载荷哈希,数值等未实现种类可明确拒绝。 pub fn hash(&self, reference: ObjectRef) -> Result { self.objects.hash(reference) } + /// eval 对象比较委托 Store 的正向、反向及身份回退;直接数值另走 eval 数学比较。 pub fn compare( &self, left: ObjectRef, @@ -232,12 +281,14 @@ impl Vm { /// Reads a borrowed value. An Object result needs retain() if the host /// wants it to survive removal of the attribute that currently owns it. + /// 宿主借用属性值,方法以函数句柄返回,不分配接收者绑定对象。 pub fn get_attr(&self, reference: ObjectRef, name: &str) -> Result { self.objects.get_attr(reference, name) } /// Object-valued attributes own internal strong references independently /// of the caller's external references. Failure leaves the field unchanged. + /// 宿主/执行器写属性,值对象由字段独立强边持有,不消耗调用者外部根。 pub fn set_attr( &mut self, reference: ObjectRef, @@ -247,20 +298,24 @@ impl Vm { self.objects.set_attr(reference, name, value) } + /// 宿主/执行器删除属性,经类型字段保护和转发定位后释放原值内部边。 pub fn del_attr(&mut self, reference: ObjectRef, name: &str) -> Result<(), VmError> { self.objects.del_attr(reference, name) } + /// eval 验证借用 Value 中对象句柄的堆归属和存活,不 retain 或新增读取依赖。 pub(crate) fn validate_value(&self, value: &Value) -> Result<(), VmError> { self.objects.validate_value(value) } /// Explicit full collection. Execution slots use owned roots, so values /// on live execution stacks participate in the same root accounting. + /// 宿主显式回收外部根不可达的环;活跃帧根与停放协程边参与同一对象图计数。 pub fn collect_cycles(&mut self) -> usize { self.objects.collect_cycles() } + /// eval/宿主借用具体 Rust 载荷,expected 只是错误标签,不等于名义类型注解。 pub fn payload( &self, reference: ObjectRef, @@ -269,14 +324,17 @@ impl Vm { self.objects.payload::(reference, expected) } + /// 宿主创建不可变堆字符串,返回拥有根;与直接 Value::Text 区分。 pub fn new_string(&mut self, text: impl Into) -> Result { self.objects.new_string(text) } + /// 编译器加载/宿主把已校验 EndorCode 分配成托管代码对象,常量强边由 Store 维护。 pub fn new_code(&mut self, code: EndorCode) -> Result { self.objects.new_code(code) } + /// eval 执行入口/闭包构造与宿主创建源码函数,Store 校验代码/默认值/捕获槽并捕获内置空间。 pub fn new_function( &mut self, name: impl Into, @@ -289,23 +347,28 @@ impl Vm { .new_function(name, code, globals, defaults, closure) } + /// 宿主创建堆数值对象,返回拥有根;直接 Value::Number 不占该堆槽。 pub fn new_number(&mut self, number: EndorNumber) -> Result { self.objects.new_number(number) } + /// eval 字典构造/宿主创建空插入顺序字典,返回拥有根。 pub fn new_dict(&mut self) -> Result { self.objects.new_dict() } /// Returns borrowed keys and values in insertion order. + /// eval 构类及宿主借用插入顺序条目,观察整个字典,不新增对象根。 pub fn dict_items(&self, dictionary: ObjectRef) -> Result<&[(Value, Value)], VmError> { self.objects.dict_items(dictionary) } + /// eval/宿主通用键读取,缺键 None 与已存 Nil 分开,结果对象句柄仍借用。 pub fn dict_get(&self, dictionary: ObjectRef, key: &Value) -> Result, VmError> { self.objects.dict_get(dictionary, key) } + /// eval 构图/全局赋值与宿主写字典,Store 保留首键及插入顺序并事务维护强边。 pub fn dict_set( &mut self, dictionary: ObjectRef, @@ -315,27 +378,33 @@ impl Vm { self.objects.dict_set(dictionary, key, value) } + /// eval 删除全局名/宿主删除键,缺键 false,不存在时不产生替换通知。 pub fn dict_delete(&mut self, dictionary: ObjectRef, key: &Value) -> Result { self.objects.dict_delete(dictionary, key) } + /// 宿主清空字典,保留对象身份,旧键值边由 Heap 释放;空字典也受写屏障。 pub fn dict_clear(&mut self, dictionary: ObjectRef) -> Result<(), VmError> { self.objects.dict_clear(dictionary) } + /// eval 把局部提升为闭包共享槽时创建,None 为未初始化,Some(Nil) 是已初始化。 pub fn new_cell(&mut self, value: Option) -> Result { self.objects.new_cell(value) } /// Reads a borrowed value; an empty cell is not Value::Nil. + /// eval 自由变量读取/宿主借用槽值,空槽返回 EmptyCell,不把 Nil 当未初始化。 pub fn cell_get(&self, cell: ObjectRef) -> Result { self.objects.cell_get(cell) } + /// eval 捕获变量赋值/宿主更新共享槽,保留 Cell 身份,引用边由 Heap 替换。 pub fn cell_set(&mut self, cell: ObjectRef, value: Value) -> Result<(), VmError> { self.objects.cell_set(cell, value) } + /// eval 删除捕获变量/宿主恢复空槽,释放旧值边,不把它替换为 Some(Nil)。 pub fn cell_clear(&mut self, cell: ObjectRef) -> Result<(), VmError> { self.objects.cell_clear(cell) } -- 2.52.0 From 8e190b25ec8e3688764c3b6eaa753db8f9daf549 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:49:09 +0800 Subject: [PATCH 19/62] docs(core): explain synchronous and sliced readonly calls --- crates/core/src/eval.rs | 67 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/crates/core/src/eval.rs b/crates/core/src/eval.rs index 534ee20..617926a 100644 --- a/crates/core/src/eval.rs +++ b/crates/core/src/eval.rs @@ -1,3 +1,7 @@ +//! VM 的指令执行与调用生命周期;宿主入口在此准备帧,底层对象规则仍通过 Vm/Store 操作。 +//! 执行结果中的对象拥有一份外部根,参数通常借用;帧/原生上下文负责保留和清理执行期间根。 +//! 只读续执行跨 Pending 保持 Heap 作用域;托管可变协程另存载荷,不沿用该全局作用域。 + use crate::ExecutionProgress; use crate::frame::{Coroutine, CoroutineState, NativeReturn, StackEntry, SuspendedCall}; use crate::{ @@ -8,11 +12,15 @@ use crate::{ use crate::{CoroutineProgress, ObjectPayload}; use crate::{NativeContext, NativeResult}; +/// prepare_call 统一结果,成功的 Value/Frame 都携带执行期间已取得的拥有根。 enum PreparedCall { + // 原生或委托调用在准备阶段直接完成,结果根移交上层。 Value(Value), + // 源码帧已绑定参数并持有根,交给循环执行或暂存。 Frame(EndorFrame), } +/// 求值器构造携带代码单元偏移的字节码错误,不将偏移误报为源码行。 fn invalid(offset: usize, reason: &'static str) -> VmError { VmError::InvalidBytecode { offset, reason } } @@ -21,24 +29,31 @@ impl Vm { /// Execute a code object in a globals dictionary. Like CPython's EvalCode /// entry path, this constructs a temporary function and enters its frame. /// Object results own one external reference: release_value() when done. + /// 宿主执行已验证代码,使用默认预算,最终对象结果归调用方拥有。 pub fn execute(&mut self, code: ObjectRef, globals: ObjectRef) -> Result { self.execute_with_limits(code, globals, ExecutionLimits::default()) } + /// 宿主以指定预算执行代码,临时源码函数包装全局/代码引用,调用结束后释放包装根。 pub fn execute_with_limits( &mut self, code: ObjectRef, globals: ObjectRef, limits: ExecutionLimits, ) -> Result { + // 新调用从空诊断开始,后续失败才写入该次剩余帧回溯。 self.last_traceback.clear(); + // 借用代码载荷的诊断名后转为拥有 String,避免跨可变分配保留载荷借用。 let name = self .payload::(code, "code")? .code() .name() .to_owned(); + // 创建无参数默认值/无捕获的临时执行函数;代码所需布局仍由函数校验。 let function = self.new_function(name, code, globals, Vec::new(), Vec::new())?; + // 先保存调用结果,使成功和错误都能在返回前释放临时函数根。 let result = self.call_with_limits(function, &[], limits); + // 结果对象已由调用路径保留,释放包装函数不应使返回结果失效。 self.release(function)?; result } @@ -46,6 +61,7 @@ impl Vm { /// Dispatch both language and host functions. Arguments are borrowed; /// every Object result owns one external reference, including native /// callbacks that return one of their borrowed arguments. + /// 宿主同步调用源码/原生函数,参数借用,返回对象根需 release_value。 pub fn call(&mut self, function: ObjectRef, arguments: &[Value]) -> Result { self.call_with_limits(function, arguments, ExecutionLimits::default()) } @@ -53,12 +69,14 @@ impl Vm { /// Allow result allocation while rejecting mutations of objects that were /// already alive on entry. All reads share the call's normal frame budget. /// Returned read handles are borrowed dependency identities, not owned roots. + /// 宿主同步只读兼容入口,只保留对象读取集合,丢弃精确依赖键。 pub fn call_read_only_with_limits( &mut self, function: ObjectRef, arguments: &[Value], limits: ExecutionLimits, ) -> Result<(Value, Vec), VmError> { + // 精确入口统一维护只读作用域与结果所有权,这里只改变返回观察的形状。 let (value, reads) = self.call_read_only_precise_with_limits(function, arguments, limits)?; Ok((value, reads.objects)) @@ -67,43 +85,56 @@ impl Vm { /// Synchronous read-only call with precise observations as well as the /// conservative object list. Result ownership and hard limits match call(). /// ReadSet and all identities/value snapshots inside it are borrowed only. + /// 宿主同步只读入口,建立保护快照,成功返回拥有结果与借用观察集合。 pub fn call_read_only_precise_with_limits( &mut self, function: ObjectRef, arguments: &[Value], limits: ExecutionLimits, ) -> Result<(Value, crate::ReadSet), VmError> { + // 快照进入前对象身份;之后新建结果允许初始化,但已有对象受写屏障保护。 let scope = self.objects.begin_read_only(); + // 先保存调用结果,使成功和错误都能在返回前释放临时函数根。 let result = self.call_with_limits(function, arguments, limits); + // 结束域先取回借用观察,之后才将调用成功值与观察组合返回。 let reads = self.objects.end_read_only_precise(scope)?; result.map(|value| (value, reads)) } + /// 同步调用核心:准备可立即完成的原生结果或源码帧,执行后清理帧及保存错误回溯。 pub fn call_with_limits( &mut self, function: ObjectRef, arguments: &[Value], limits: ExecutionLimits, ) -> Result { + // 已有只读续执行保留活跃作用域,禁止另开同步调用干扰它。 if self.suspended_call.is_some() { return Err(VmError::UnsupportedOperation( "call while read-only execution is suspended", )); } + // 新调用从空诊断开始,后续失败才写入该次剩余帧回溯。 self.last_traceback.clear(); + // 累计调用准备和指令工作量,切片恢复不会将其清零。 let mut executed = 0; + // 原生调用可能在准备阶段完成并直接移交拥有结果,源码则进入帧循环。 let frame = match self.prepare_call(function, arguments.to_vec(), &mut executed, limits)? { PreparedCall::Value(value) => return Ok(value), PreparedCall::Frame(frame) => frame, }; + // 源码帧已创建但预算不允许执行,必须先清理所持根再报错。 if limits.max_frames == 0 { self.clear_frame(frame); return Err(VmError::ExecutionLimit("frames")); } + // 显式帧向量代替 Rust 递归调用,调用深度由硬预算控制。 let mut frames = vec![frame]; + // 同步入口没有软切片限制且不允许 Pending await;只读恢复另按 slice_end 执行。 let result = self .eval_frames(&mut frames, limits, &mut executed, usize::MAX, false) .map(|value| value.expect("unbounded slice completes or fails")); + // 错误时先保存诊断,再销毁帧;使用 current_offset 而不是下一指令偏移。 if result.is_err() { self.last_traceback = frames .iter() @@ -115,6 +146,7 @@ impl Vm { }) .collect(); } + // 逐帧回收栈、局部、函数和返回约束根,不把 Rust drop 误当托管 release。 while let Some(frame) = frames.pop() { self.clear_frame(frame); } @@ -124,6 +156,7 @@ impl Vm { /// Begin a resumable read-only call. One VM owns at most one continuation. /// A slice is a soft instruction quantum: native callbacks and delegation /// complete atomically and remain subject to the cumulative hard limits. + /// 宿主开始单个可分片只读调用,原生准备可立即完成,源码帧停放在 VM 内。 pub fn start_read_only_call( &mut self, function: ObjectRef, @@ -131,22 +164,32 @@ impl Vm { limits: ExecutionLimits, slice: usize, ) -> Result { + // 拒绝零量子,避免调度者永远不推进且仍报告 Pending。 if slice == 0 { return Err(VmError::InvalidArgument("execution slice must be positive")); } + // 只读续执行只能在没有其他续执行或只读域时启动,避免作用域令牌交错。 if self.suspended_call.is_some() || self.objects.is_read_only() { return Err(VmError::UnsupportedOperation( "start suspended call inside another execution", )); } + // 新调用从空诊断开始,后续失败才写入该次剩余帧回溯。 self.last_traceback.clear(); + // 快照进入前对象身份;之后新建结果允许初始化,但已有对象受写屏障保护。 let scope = self.objects.begin_read_only(); + // 累计调用准备和指令工作量,切片恢复不会将其清零。 let mut executed = 0; + // 准备原生调用/委托可能消耗预算或报错,先捕获结果以便三种分支都结束/保存作用域。 let prepared = self.prepare_call(function, arguments.to_vec(), &mut executed, limits); + // 成功直接值关闭域;源码帧保存域;准备失败关闭域后传播错误。 match prepared { + // 原生立即完成,无源码帧要保存,但仍需结束观察收集。 Ok(PreparedCall::Value(value)) => { + // 若读取域收尾失败,必须归还已经取得的结果根,不能泄漏。 let reads = match self.objects.end_read_only_precise(scope) { Ok(reads) => reads, + // 出错分支在返回前处理本分支拥有的结果/作用域,不能保留半完成状态。 Err(error) => { self.drop_root(value); return Err(error); @@ -159,21 +202,25 @@ impl Vm { instructions: executed, }) } + // 源帧尚未执行,先通过帧预算再交给 VM 的单个续执行槽。 Ok(PreparedCall::Frame(frame)) => { + // 源码帧已创建但预算不允许执行,必须先清理所持根再报错。 if limits.max_frames == 0 { self.clear_frame(frame); self.objects.end_read_only(scope)?; return Err(VmError::ExecutionLimit("frames")); } + // 保存根仍由帧持有,Heap 只读作用域跨后续 Pending 不关闭。 self.suspended_call = Some(SuspendedCall { frames: vec![frame], scope, limits, executed, }); - // Preparation counts toward the first quantum as well. + // 首片边界从零计,调用准备已用计数也占首片软预算,不另赠一片。 self.resume_read_only_to(slice) } + // 出错分支在返回前处理本分支拥有的结果/作用域,不能保留半完成状态。 Err(error) => { self.objects.end_read_only(scope)?; Err(error) @@ -181,16 +228,20 @@ impl Vm { } } + /// 调度者判断是否有只读续执行,不表示托管协程数量或等待状态。 pub fn has_suspended_call(&self) -> bool { self.suspended_call.is_some() } /// Resume existing frames, never replay earlier instructions. The read-only /// scope remains active across Pending; preexisting heap writes stay barred. + /// 宿主从已保存指令位置继续,预算跨片累计,不能重放已执行语句。 pub fn resume_read_only_call(&mut self, slice: usize) -> Result { + // 拒绝零量子,避免调度者永远不推进且仍报告 Pending。 if slice == 0 { return Err(VmError::InvalidArgument("execution slice must be positive")); } + // 读取此前累计计数,新增软边界以 saturating_add 防整数绕回。 let executed = self .suspended_call .as_ref() @@ -199,11 +250,14 @@ impl Vm { self.resume_read_only_to(executed.saturating_add(slice)) } + /// start/resume 共用的累计软边界执行器;Pending 原样放回状态,终态关闭只读域。 fn resume_read_only_to(&mut self, slice_end: usize) -> Result { + // 暂时取走续执行状态独占推进;只有 Pending 才重新放回,终态不残留槽。 let mut call = self .suspended_call .take() .ok_or(VmError::InvalidArgument("no suspended call"))?; + // 按累计 slice_end 推进现有帧;false 禁止 await 停放,None 表示软切片结束。 let result = self.eval_frames( &mut call.frames, call.limits, @@ -211,11 +265,15 @@ impl Vm { slice_end, false, ); + // 软切片耗尽:保留原帧/作用域/硬预算计数,不清理根或重新准备函数。 if let Ok(None) = result { + // 报告从调用起点累计的工作量,不是当前片局部数量。 let instructions = call.executed; + // 将完整续执行状态归还 VM,原有只读屏障继续生效。 self.suspended_call = Some(call); return Ok(ExecutionProgress::Pending { instructions }); } + // 错误时先保存诊断,再销毁帧;使用 current_offset 而不是下一指令偏移。 if result.is_err() { self.last_traceback = call .frames @@ -228,10 +286,13 @@ impl Vm { }) .collect(); } + // 逐帧回收栈、局部、函数和返回约束根,不把 Rust drop 误当托管 release。 while let Some(frame) = call.frames.pop() { self.clear_frame(frame); } + // 完成/失败统一关闭作用域,保留收尾错误供下方与执行结果一起处理。 let reads = self.objects.end_read_only_precise(call.scope); + // 仅执行与收尾均成功才交付结果;收尾失败释放成功结果,执行失败优先传播执行错误。 match (result, reads) { (Ok(Some(value)), Ok(reads)) => Ok(ExecutionProgress::Complete { value, @@ -239,6 +300,7 @@ impl Vm { reads: reads.objects, instructions: call.executed, }), + // 已产生的成功结果此时不能交付,归还其对象根避免泄漏。 (Ok(Some(value)), Err(error)) => { self.drop_root(value); Err(error) @@ -249,10 +311,13 @@ impl Vm { } /// Release all retained execution roots and close the read-only scope. + /// 宿主放弃当前只读续执行,逐帧释放根并关闭作用域,无任务时返回 false。 pub fn cancel_read_only_call(&mut self) -> Result { + // 没有续执行是正常无操作;有任务则一次取走,后续取消不会重复释放。 let Some(mut call) = self.suspended_call.take() else { return Ok(false); }; + // 逐帧回收栈、局部、函数和返回约束根,不把 Rust drop 误当托管 release。 while let Some(frame) = call.frames.pop() { self.clear_frame(frame); } -- 2.52.0 From 11703b73318cba0cfaf14c28801ffb84e80ce2de Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:50:44 +0800 Subject: [PATCH 20/62] docs(core): explain managed coroutine root transfers and cleanup --- crates/core/src/eval.rs | 73 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/crates/core/src/eval.rs b/crates/core/src/eval.rs index 617926a..d562cb9 100644 --- a/crates/core/src/eval.rs +++ b/crates/core/src/eval.rs @@ -328,18 +328,26 @@ impl Vm { /// Prepare a managed independently resumable call. Native delegation may /// run during preparation; source instructions start only on resume. /// The returned handle owns one external reference, like every allocation. + /// 宿主任务系统准备独立可恢复调用,返回一份协程拥有根;原生准备可发生在首次 resume 之前。 pub fn new_coroutine( &mut self, function: ObjectRef, arguments: &[Value], limits: ExecutionLimits, ) -> Result { + // 先拒绝只读上下文,避免用托管协程绕过活跃只读保护。 self.check_coroutine_entry()?; + // 本次真正执行前清理旧诊断,错误才记录新的帧位置。 self.last_traceback.clear(); + // 首次调用准备也计入累计预算,后续恢复不能清零。 let mut executed = 0; + // prepare_call 可能直接完成原生调用或返回已持根的源码帧。 let prepared = self.prepare_call(function, arguments.to_vec(), &mut executed, limits)?; + // 源码帧暂存 Frames,立即结果暂存 Ready;两者尚持有执行根待转换。 let state = match prepared { + // 新源码帧先核对硬帧上限,零预算必须清理已取得的帧根。 PreparedCall::Frame(frame) => { + // 构造执行帧不等于已获执行许可,失败归还其全部拥有根。 if limits.max_frames == 0 { self.clear_frame(frame); return Err(VmError::ExecutionLimit("frames")); @@ -348,22 +356,29 @@ impl Vm { } PreparedCall::Value(value) => CoroutineState::Ready(value), }; - // Allocate traced edges before releasing the original execution roots. + // 先建立托管载荷强边,再归还准备阶段根,确保转换期间对象始终存活。 + // 把准备状态包装为托管载荷,trace 会为其保留与帧根一致的内部强边。 let coroutine = Coroutine { state, limits, executed, }; + // 枚举每一份根身份,重复项表示多份所有权,不能去重。 let mut roots = Vec::new(); + // 先快照原执行根,分配后载荷内部边接管这些引用。 coroutine.trace(&mut |reference| roots.push(reference)); + // 分配成功先建立内部边,失败则没有托管接管者;两种情况都要归还原执行根。 let result = self.allocate(coroutine); + // 逐项归还原执行根;成功路径已由载荷持边,错误路径则释放准备状态资源。 for root in roots { self.release(root)?; } result } + /// 创建/恢复/取消共用门禁,禁止在全局只读续执行或其他只读域内进行协程操作。 fn check_coroutine_entry(&self) -> Result<(), VmError> { + // 可变协程不持有全局只读域,也不能干扰 VM 已停放的只读调用。 if self.has_suspended_call() || self.objects.is_read_only() { return Err(VmError::UnsupportedOperation( "coroutine operation inside read-only execution", @@ -375,37 +390,52 @@ impl Vm { /// Resume one managed call, preserving cumulative hard limits. Other calls /// and heap writes may execute between Pending results. No prior instruction /// is replayed. Completion returns an owned value and consumes the frames. + /// 宿主推进一个协程量子,先临时保留协程自身,防回调移除其最后外部来源。 pub fn resume_coroutine( &mut self, reference: ObjectRef, slice: usize, ) -> Result { + // 临时拥有根覆盖整个恢复,包括原生回调可能修改持有协程的对象图。 self.retain(reference)?; + // 保存执行结果,使错误返回也经过下方协程临时根释放。 let result = self.resume_coroutine_inner(reference, slice); + // 归还仅由本次推进取得的保护根,不消耗调用者原有根。 self.release(reference)?; result } + /// 恢复状态、把载荷边转为执行根,执行后再停放或提交 Finished;外层负责保护协程本身。 fn resume_coroutine_inner( &mut self, reference: ObjectRef, slice: usize, ) -> Result { + // 先拒绝只读上下文,避免用托管协程绕过活跃只读保护。 self.check_coroutine_entry()?; + // 软量子必须正数,否则没有可验证的推进工作。 if slice == 0 { return Err(VmError::InvalidArgument("execution slice must be positive")); } + // 把准备状态包装为托管载荷,trace 会为其保留与帧根一致的内部强边。 let coroutine = self.payload::(reference, "coroutine")?; + // 硬限制来自创建时状态,不能在每个量子重新给予预算。 let limits = coroutine.limits; + // 从上次停放的累计计数继续,而不是从零执行。 let mut executed = coroutine.executed; + // 只复制状态表示/Arc,不因 clone 增加托管计数;先由旧载荷边保证存活。 let state = match &coroutine.state { CoroutineState::Frames(frames) => CoroutineState::Frames(frames.clone()), CoroutineState::Ready(value) => CoroutineState::Ready(value.clone()), _ => return Err(VmError::InvalidArgument("coroutine is running or finished")), }; + // 枚举每一份根身份,重复项表示多份所有权,不能去重。 let mut roots = Vec::new(); + // 列出恢复为活跃执行时所需全部根;Ready 标量没有对象根。 match &state { + // 帧身份沿同一 trace 清单转换,不复制执行行为或重新绑定实参。 CoroutineState::Frames(frames) => { + // 复用 frame::trace_roots,确保与活跃帧清理清单完全对应。 for frame in frames.iter() { frame.trace_roots(&mut |root| roots.push(root)); } @@ -413,11 +443,14 @@ impl Vm { CoroutineState::Ready(Value::Object(root)) => roots.push(*root), _ => {} } - // Transfer edges to execution roots transactionally, before replacing - // the managed payload. Duplicate edges must retain duplicate roots. + // 恢复时方向相反:先取得每份执行根,再替换旧载荷以释放内部边;重复边对应重复根。 + // 事务式取得新执行根;中途失败仅归还本轮已成功 retain 的部分。 let mut retained = Vec::new(); + // 逐项取得恢复所需执行根,旧载荷在全部取得成功前保持不变。 for root in roots { + // 任何一个根无法保留就不替换旧载荷,原停放状态仍有效。 if let Err(error) = self.retain(root) { + // 归还本轮新加根,不释放仍属于旧载荷的内部边。 for root in retained { self.release(root)?; } @@ -425,6 +458,7 @@ impl Vm { } retained.push(root); } + // 状态替换也可能失败,调用方必须清理已保留根或尚未交付的结果。 if let Err(error) = self.replace_payload( reference, Coroutine { @@ -433,17 +467,23 @@ impl Vm { executed, }, ) { + // 归还本轮新加根,不释放仍属于旧载荷的内部边。 for root in retained { self.release(root)?; } return Err(error); } + // 本次真正执行前清理旧诊断,错误才记录新的帧位置。 self.last_traceback.clear(); + // Running 替换释放旧载荷 Arc 后,快照 Arc 应成为唯一所有者,可取回帧向量。 let mut frames = match state { + // 帧身份沿同一 trace 清单转换,不复制执行行为或重新绑定实参。 CoroutineState::Frames(frames) => { std::sync::Arc::try_unwrap(frames).expect("private frame storage has one owner") } + // 准备阶段已完成,直接提交 Finished,再把新取得的结果根交给调用者。 CoroutineState::Ready(value) => { + // 状态替换也可能失败,调用方必须清理已保留根或尚未交付的结果。 if let Err(error) = self.replace_payload( reference, Coroutine { @@ -462,20 +502,29 @@ impl Vm { } _ => unreachable!(), }; + // 软边界按累计计数增加,saturating_add 防边界绕回,硬上限仍独立检查。 let slice_end = executed.saturating_add(slice); + // 允许 await 停放;None 既可能是软量子结束,也可能等待对象 Pending。 let result = self.eval_frames(&mut frames, limits, &mut executed, slice_end, true); + // 需要停放:先分析是否 await,再把完整帧/计数提交回托管载荷。 if let Ok(None) = result { + // waiting 只取最内层帧等待对象;栈顶操作数留存,恢复时不会重放创建它的调用。 let waiting = frames.last().filter(|frame| frame.waiting).map(|frame| { + // 已执行 Await 证明栈顶是对象,否则属于执行器内部不变量破坏。 match Self::top_value(frame).expect("await operand remains on stack") { Value::Object(reference) => reference, _ => unreachable!(), } }); + // 把帧向量重新装进共享快照,内部边接管前仍持原执行根。 let frames = std::sync::Arc::new(frames); + // 枚举每一份根身份,重复项表示多份所有权,不能去重。 let mut roots = Vec::new(); + // 复用 frame::trace_roots,确保与活跃帧清理清单完全对应。 for frame in frames.iter() { frame.trace_roots(&mut |root| roots.push(root)); } + // 先提交 Frames 与完整强边,发布成功前不能释放原执行根造成悬空。 let published = self.replace_payload( reference, Coroutine { @@ -484,10 +533,13 @@ impl Vm { executed, }, ); + // 逐项归还原执行根;成功路径已由载荷持边,错误路径则释放准备状态资源。 for root in roots { self.release(root)?; } + // 归还原执行根后传播发布错误;失败不会交付虚假的 Pending 状态。 published?; + // Waiting 只返回借用等待身份,图由停放协程帧维持,调度者须保留协程根。 if let Some(awaitable) = waiting { return Ok(CoroutineProgress::Waiting { awaitable, @@ -498,6 +550,7 @@ impl Vm { instructions: executed, }); } + // 在帧清理前提取回溯,保存 current_offset 而非下一指令位置。 if result.is_err() { self.last_traceback = frames .iter() @@ -509,9 +562,11 @@ impl Vm { }) .collect(); } + // 终态清理所有剩余活跃帧根,成功返回值已先从栈中移出。 while let Some(frame) = frames.pop() { self.clear_frame(frame); } + // 无论成功或失败都清空运行状态,保留预算/计数供诊断,Finished 不再持帧图。 let finished = self.replace_payload( reference, Coroutine { @@ -520,8 +575,11 @@ impl Vm { executed, }, ); + // 成功必须同时完成状态提交;提交失败归还结果,执行错误路径仍须处理收尾结果。 match result { + // 结果拥有根仅在 Finished 提交成功后移交给宿主。 Ok(Some(value)) => { + // 结果已产生但状态提交失败,不能泄漏该结果根。 if let Err(error) = finished { self.drop_root(value); return Err(error); @@ -531,6 +589,7 @@ impl Vm { instructions: executed, }) } + // 此处先确保 Finished 发布成功,再返回原执行错误。 Err(error) => { finished?; Err(error) @@ -542,16 +601,23 @@ impl Vm { /// Cancellation releases managed frame edges; dropping an unreachable /// coroutine has the same ownership effect, including cycles through locals. /// Writes performed by previous quanta are never rolled back. + /// 宿主取消停放帧或准备结果,用 Finished 替换以释放载荷边;不回滚此前写入。 pub fn cancel_coroutine(&mut self, reference: ObjectRef) -> Result { + // 先拒绝只读上下文,避免用托管协程绕过活跃只读保护。 self.check_coroutine_entry()?; + // 把准备状态包装为托管载荷,trace 会为其保留与帧根一致的内部强边。 let coroutine = self.payload::(reference, "coroutine")?; + // 正执行状态拒绝取消,避免在 Rust 回调重入时移除执行者状态。 if matches!(coroutine.state, CoroutineState::Running) { return Err(VmError::InvalidArgument("coroutine is running")); } + // 重复取消是正常 false,不再修改载荷版本。 if matches!(coroutine.state, CoroutineState::Finished) { return Ok(false); } + // 硬限制来自创建时状态,不能在每个量子重新给予预算。 let limits = coroutine.limits; + // 取消保留累计诊断计数,不把已经执行的副作用或预算消耗抹除。 let executed = coroutine.executed; self.replace_payload( reference, @@ -566,6 +632,7 @@ impl Vm { /// Outer-to-inner execution locations from the last failed call/execute. /// These are copied metadata, not heap references or language exceptions. + /// 宿主错误报告借用最近失败的复制位置元数据,不保留故障对象图。 pub fn last_traceback(&self) -> &[ExecutionLocation] { &self.last_traceback } -- 2.52.0 From 6fddeedef316b72f0401d3df3a2e0f24a1c6db59 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:52:09 +0800 Subject: [PATCH 21/62] docs(core): explain frame initialization and execution root ownership --- crates/core/src/eval.rs | 52 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/crates/core/src/eval.rs b/crates/core/src/eval.rs index d562cb9..992ad83 100644 --- a/crates/core/src/eval.rs +++ b/crates/core/src/eval.rs @@ -637,57 +637,76 @@ impl Vm { &self.last_traceback } + /// 宿主归还 call/execute/协程完成结果,或执行器清理拥有值;标量由 Rust 释放,对象归还一份外部根。 pub fn release_value(&mut self, value: Value) -> Result<(), VmError> { + // 仅 Object 变体占用托管根,直接值不调用 release/retain。 if let Value::Object(reference) = value { self.release(reference)?; } Ok(()) } + /// 执行器把借用值放入拥有槽/结果前先验证并保留对象根,标量不需要计数。 fn retain_value(&mut self, value: &Value) -> Result<(), VmError> { + // 先检查对象来自当前堆且存活,不能为异堆/过期值建立执行根。 self.validate_value(value)?; + // 仅 Object 变体占用托管根,直接值不调用 release/retain。 if let Value::Object(reference) = value { self.retain(*reference)?; } Ok(()) } + /// 已知拥有根的内部清理入口,失败代表执行器所有权不变量破坏,不静默忽略。 fn drop_root(&mut self, value: Value) { self.release_value(value) .expect("execution owns every object root it releases"); } + /// 返回/失败/取消时统一销毁活跃帧:清理约束、栈、局部槽、函数及返回注解根。 fn clear_frame(&mut self, frame: EndorFrame) { + // 先释放委托返回约束持有的定义根,不依赖元数据 Rust Drop 管理托管计数。 self.clear_native_returns(frame.native_returns); + // 每个栈项独立拥有计数,重复对象值必须逐项归还。 for entry in frame.stack { + // Null 是协议标记,不是对象或语言 Nil,不参与根释放。 if let StackEntry::Value(value) = entry { self.drop_root(value); } } + // 未绑定 None 跳过,其余每个局部/自由变量槽拥有的对象根逐项释放。 for value in frame.localsplus.into_iter().flatten() { self.drop_root(value); } + // 函数根保证代码/全局/内置空间在帧内可达;这些借用字段不额外 release。 self.release(frame.function) .expect("frame owns its function root"); + // 帧创建时额外保留的名义/能力返回定义根,也必须单独归还。 if let Some(reference) = frame.return_type.and_then(|kind| kind.reference()) { self.release(reference) .expect("frame owns its return type root"); } } + /// prepare_call 为源码函数建立帧,先校验数量/值/契约,再取得根并初始化局部和捕获槽。 fn prepare_frame( &mut self, function: ObjectRef, arguments: &[Value], ) -> Result { + // 复制函数元数据以跨可变 Store 操作使用;clone 不是取得托管根。 let data = self .payload::(function, "function")? .clone(); + // 共享不可变 Rust 执行数据,常量图仍需后面的函数根保活。 let executable = self .payload::(data.code(), "code")? .shared_code(); + // 源码参数槽总数含显式 self,与默认值共同决定数量区间。 let maximum = executable.parameters().len(); + // 函数构造已保证默认值数量不超过参数数量,因此减法安全。 let required = maximum - data.defaults().len(); + // 数量先检查,后续默认值索引依赖 required..maximum 不变量。 if arguments.len() < required || arguments.len() > maximum { return Err(VmError::ArgumentCount { name: data.name().into(), @@ -696,12 +715,16 @@ impl Vm { provided: arguments.len(), }); } + // 所有传入对象先验证,不能等到部分槽初始化后才发现异堆参数。 for value in arguments { + // 先检查对象来自当前堆且存活,不能为异堆/过期值建立执行根。 self.validate_value(value)?; } + // 优先实参首项;仅当所有参数均可默认且无实参时才尝试首默认值作为 self。 let receiver = arguments .first() .or_else(|| { + // 至少一个必填参数时不能从默认值假造接收者。 if required == 0 { data.defaults().first() } else { @@ -712,23 +735,30 @@ impl Vm { Value::Object(receiver) => Some(*receiver), _ => None, }); + // 对象首参触发方法契约门禁;返回 bool 决定即使普通源码检查关闭是否仍要验注解。 let typed_method = if let Some(receiver) = receiver { self.objects .check_method_contract_call(function, receiver, self.source_type_checks)? } else { false }; + // 尚未解析的返回约束不拥有根,解析完成后才统一保留。 let mut return_type = None; + // 显式开关或类型化能力要求启用源码注解检查;无源码签名仍跳过这段解析。 if (self.source_type_checks || typed_method) && let Some(signature) = executable.source_signature() { + // 所有参数(含使用默认值的位置)逐个匹配,不仅检查调用方显式传入部分。 for (index, expected_name) in signature.parameters.iter().enumerate() { + // 缺失的尾参数从默认值列表按 index-required 读取。 let value = arguments .get(index) .unwrap_or_else(|| &data.defaults()[index - required]); + // 注解按函数环境和可能的接收者解析,方法自身类名可绑定真实声明定义。 let expected = self .objects .annotation_type(function, expected_name, receiver)?; + // 匹配失败生成具名参数诊断,在取得帧拥有根之前退出。 if !self.objects.matches_annotation(value, expected)? { return Err(VmError::TypeMismatch { function: data.name().into(), @@ -738,19 +768,23 @@ impl Vm { }); } } + // 先解析返回约束,随后额外保留其定义根,防执行期间全局重绑定使其失效。 return_type = Some(self.objects.annotation_type( function, &signature.return_type, receiver, )?); } + // 从这里开始函数成为帧拥有根,代码/全局/内置空间通过函数内部边维持存活。 self.retain(function)?; + // 若返回类型根无法保留,归还刚取得的函数根,不创建半拥有帧。 if let Some(reference) = return_type.and_then(|kind| kind.reference()) && let Err(error) = self.retain(reference) { self.release(function)?; return Err(error); } + // 建立空槽帧;函数/返回定义根已持有,其他槽后续逐项初始化。 let mut frame = EndorFrame { return_type, native_returns: Vec::new(), @@ -767,8 +801,11 @@ impl Vm { stack: Vec::with_capacity(executable.stack_size()), executable, }; + // 局部闭包汇总初始化失败,外层 clear_frame 统一处理已成功安装的根。 let initialized = (|| -> Result<(), VmError> { + // 方法能力返回约束也要保留,后续返回需满足所有能力而非仅源码声明。 if let Some(receiver) = receiver { + // push_native_return 负责保留各类型定义,失败时其自身清理未提交部分。 for (name, expected) in self.objects.method_trait_returns(function, receiver)? { self.push_native_return( &mut frame.native_returns, @@ -776,33 +813,44 @@ impl Vm { )?; } } + // 已传参数会覆盖默认值前段,仅拼接尚缺的尾部默认值。 let defaults = data.defaults().iter().skip(arguments.len() - required); + // 实参先于缺失默认值,依次填参数前缀,其余未绑定局部仍为 None。 for (slot, value) in frame .localsplus .iter_mut() .zip(arguments.iter().chain(defaults)) { + // 先 retain 再写槽,失败槽仍空;已写槽由外层失败清理释放。 self.retain_value(value)?; *slot = Some(value.clone()); } - // Captured local cells occupy the original local slots, as in - // CPython's localsplus layout. Free cells follow the local slots. + // 捕获局部保留原局部槽位置但改存 Cell;外部自由变量 Cell 追加在全部局部之后。 + // 捕获局部不增加槽位置,仅将原值替换为拥有该值的 Cell。 let local_count = frame.executable.layout().locals.len(); + // 只遍历局部段,外部自由变量 Cell 在后续单独安装。 for (index, slot) in frame.localsplus[..local_count].iter_mut().enumerate() { + // 需要被内部闭包捕获的局部改用共享 Cell,即使原槽未绑定也创建空 Cell。 if frame.executable.is_cell_slot(index) { + // 新 Cell 的载荷强边先保留旧值,返回的一份外部根由该局部槽接管。 let cell = self.new_cell(slot.clone())?; + // 槽原值已被 Cell 内部边接管,归还原来那份槽执行根,不重复持有。 if let Some(old) = slot.replace(Value::Object(cell)) { self.drop_root(old); } } } + // 外层捕获 Cell 段紧跟所有局部槽,顺序与代码 free_variables 一致。 let first_free = frame.executable.layout().locals.len(); + // 闭包 Cell 已由函数持边,但每个活跃自由变量槽仍需独立执行根。 for (index, cell) in data.closure().iter().enumerate() { + // retain 成功才写槽;函数构造的闭包数量校验保证目标索引合法。 self.retain(*cell)?; frame.localsplus[first_free + index] = Some(Value::Object(*cell)); } Ok(()) })(); + // 失败时清理已装入槽的根及函数/返回约束,未装入槽不需要回滚。 if let Err(error) = initialized { self.clear_frame(frame); return Err(error); -- 2.52.0 From 56df4e5b35decd15c601bb3246018e38af0f22d7 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:53:34 +0800 Subject: [PATCH 22/62] docs(core): explain native dispatch and delegated return contracts --- crates/core/src/eval.rs | 46 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/crates/core/src/eval.rs b/crates/core/src/eval.rs index 992ad83..b8af5be 100644 --- a/crates/core/src/eval.rs +++ b/crates/core/src/eval.rs @@ -858,15 +858,19 @@ impl Vm { Ok(frame) } + /// prepare_call 分派非源码载荷:上下文回调交 NativeContext,旧式回调自行验证并保留返回根。 fn invoke_native( &mut self, function: ObjectRef, arguments: &[Value], ) -> Result { + // 先验证当前函数句柄,不让失效/异堆身份进入原生协议。 self.object(function)?; + // 参数起初是借用,仅验证有效性;此循环本身不取得拥有根。 for argument in arguments { self.validate_value(argument)?; } + // 对象首参可能是方法接收者,普通覆盖和显式能力检查不可只靠原生数量约束。 if let Some(Value::Object(receiver)) = arguments.first() { self.objects.check_method_contract_call( function, @@ -874,6 +878,7 @@ impl Vm { self.source_type_checks, )?; } + // 已发布的数量区间必须满足,缺少元数据不在此处猜测数量。 if let Some(arity) = self.objects.call_arity(function)? && !arity.accepts(arguments.len()) { @@ -884,22 +889,30 @@ impl Vm { provided: arguments.len(), }); } + // 克隆到的分派器 Arc 不借用载荷,可在 NativeContext 中安全可变访问 Store。 if let Some(callable) = self.object(function)?.payload().native_callable() { return NativeContext::execute(&mut self.objects, function, arguments, callable); } + // 无上下文旧式协议读取完整签名;有签名才检查参数/效果及结果。 let signature = self.objects.native_signature(function)?; + // 复制诊断类别名,不在执行回调后依赖之前的载荷借用。 let name = self.object(function)?.payload().kind_name().to_owned(); + // 完整签名存在才执行相应参数或返回检查,缺失时维持旧协议行为。 if let Some(signature) = &signature { signature.check_arguments(&self.objects, &name, arguments)?; } + // 旧式回调返回借用值,不能立即作为拥有结果交给宿主。 let result = self.object(function)?.payload().call(arguments)?; + // 完整签名存在才执行相应参数或返回检查,缺失时维持旧协议行为。 if let Some(signature) = &signature { signature.check_return(&self.objects, &name, &result)?; } + // 验证并取得结果根,即使回调原样返回某个参数,也必须给结果独立所有权。 self.retain_value(&result)?; Ok(NativeResult::Return(result)) } + /// 同步调用、只读续执行、协程及 Call 指令共用准备循环,原生委托不递归进入求值器。 fn prepare_call( &mut self, mut function: ObjectRef, @@ -907,42 +920,59 @@ impl Vm { executed: &mut usize, limits: ExecutionLimits, ) -> Result { + // 最初函数/实参借用;第一次 Invoke 后才接管 NativeContext 移交的函数/参数根。 let mut owned = false; + // 累计原生/能力返回约束,后续委托每一层完成都仍须满足这些要求。 let mut returns = Vec::new(); + // 闭包把委托循环的提前错误汇总,外层统一清理尚未移交的约束与输入根。 let result = (|| loop { + // 源码载荷交给 prepare_frame,不走原生调用协议或 Rust 递归执行。 if self .object(function)? .payload() .as_any() .is::() { + // 源码帧取得自己的函数/参数根,后面归还委托输入根不会使帧失效。 let mut frame = self.prepare_frame(function, &arguments)?; + // 暂取帧本地能力返回约束,将外层原生委托约束放在前面。 let mut local = std::mem::take(&mut frame.native_returns); + // 把累计约束所有权移入源码帧,外层 returns 变空,避免统一清理重复归还。 frame.native_returns = std::mem::take(&mut returns); + // 本地约束追加在外层之后,最终返回时按反序检查。 frame.native_returns.append(&mut local); return Ok(PreparedCall::Frame(frame)); } + // 每次原生/委托分派前检查累计硬预算,避免无限原生委托链绕过指令限制。 if *executed >= limits.max_instructions { return Err(VmError::ExecutionLimit("instructions")); } *executed += 1; + // 非 Any 返回约束需要跨委托保留,不能只校验中间包装器直接返回值。 if let Some(signature) = self.objects.native_signature(function)? && !signature.returns.is_any() { + // 诊断名与解析后的返回类型一并保存,不在最终返回时重查可能被重绑定的全局。 let check = NativeReturn { name: self.object(function)?.payload().kind_name().into(), expected: signature.returns, }; self.push_native_return(&mut returns, check)?; } + // 对象首参可能是方法接收者,普通覆盖和显式能力检查不可只靠原生数量约束。 if let Some(Value::Object(receiver)) = arguments.first() { + // 有效接收者的全部能力返回约束同样跨委托保留。 for (name, expected) in self.objects.method_trait_returns(function, *receiver)? { self.push_native_return(&mut returns, NativeReturn { name, expected })?; } } + // 结果已经拥有:Return 的结果根或 Invoke 的函数/各参数根由本循环接管。 match self.invoke_native(function, &arguments)? { + // 最终直接结果要通过委托链所有返回检查后才可交付。 NativeResult::Return(value) => { + // 从最内层到外层校验,每项约束都必须满足。 for check in returns.iter().rev() { + // 检查失败先归还本次结果根,再让外层清理链上剩余资源。 if let Err(error) = check.check(&self.objects, &value) { self.drop_root(value); return Err(error); @@ -954,22 +984,29 @@ impl Vm { function: next, arguments: values, } => { + // 只有已接管 Invoke 输入时才归还当前函数/实参,初始借用输入不能释放。 if owned { self.release(function)?; + // 先清空旧拥有参数列表并逐项归还,随后安装新委托输出。 for value in arguments.drain(..) { self.release_value(value)?; } } + // 新委托函数/参数根已经由 NativeContext 保留,赋值只转交清理责任。 function = next; arguments = values; + // 从此循环退出时须统一归还当前委托输入;返回结果或源码帧各自已保活。 owned = true; } } })(); + // 直接完成/错误清理仍在本地的约束,已移入源码帧的那部分不再在此表中。 self.clear_native_returns(returns); + // 只有已接管 Invoke 输入时才归还当前函数/实参,初始借用输入不能释放。 if owned { self.release(function) .expect("delegated call owns its function"); + // 循环结束时归还最后一批拥有参数,重复对象参数逐项计数。 for value in arguments { self.drop_root(value); } @@ -977,14 +1014,19 @@ impl Vm { result } + /// 委托链保留返回约束定义根,全部 retain 成功后才追加检查项,中途失败回滚已保留部分。 fn push_native_return( &mut self, returns: &mut Vec, check: NativeReturn, ) -> Result<(), VmError> { + // 先记成功 retain 的定义身份,未全部成功前不提交检查项。 let mut retained = Vec::new(); + // 枚举联合返回类型内全部定义引用,重复引用不能去重。 for reference in check.expected.references() { + // 后续定义根失败时,归还此前成功取得的根,不留下半安装约束。 if let Err(error) = self.retain(reference) { + // 回滚只处理本次取得的根,不触碰原签名载荷拥有边。 for reference in retained { self.release(reference)?; } @@ -992,11 +1034,15 @@ impl Vm { } retained.push(reference); } + // 全部定义根已取得,追加检查项后由返回链/帧负责清理。 returns.push(check); Ok(()) } + /// 委托结束/帧销毁统一归还返回约束定义根,每个引用按拥有次数独立 release。 fn clear_native_returns(&mut self, returns: Vec) { + // 销毁每份检查项持有的定义根,Rust CallType 的 drop 不会自动 release。 for check in returns { + // 枚举联合返回类型内全部定义引用,重复引用不能去重。 for reference in check.expected.references() { self.release(reference) .expect("native return check owns its type"); -- 2.52.0 From 8e800dd0b811505b5958b526e7ef571907955f9c Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:54:50 +0800 Subject: [PATCH 23/62] docs(core): explain stack values comparisons and object builders --- crates/core/src/eval.rs | 52 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/crates/core/src/eval.rs b/crates/core/src/eval.rs index b8af5be..f5ec9b8 100644 --- a/crates/core/src/eval.rs +++ b/crates/core/src/eval.rs @@ -1050,7 +1050,9 @@ impl Vm { } } + /// 各操作码按下标读取语言值,返回 clone 的借用表示,不 retain;Null 或无对应项报字节码错误。 fn stack_value(frame: &EndorFrame, index: usize) -> Result { + // Rust clone 仅复制 Value/句柄,不能把借用读取当成新拥有根。 match frame.stack.get(index) { Some(StackEntry::Value(value)) => Ok(value.clone()), _ => Err(invalid( @@ -1060,15 +1062,18 @@ impl Vm { } } + /// 操作码读取栈顶语言值的快捷入口;栈非空由代码验证保证。 fn top_value(frame: &EndorFrame) -> Result { Self::stack_value(frame, frame.stack.len() - 1) } + /// StoreFast/StoreGlobal/StoreDeref 共用赋值选择:对象相同或共享引用原地更新成功时保留旧身份。 fn assignment_value( &mut self, previous: Option, incoming: Value, ) -> Result { + // 只有新旧均为对象才尝试原地赋值;标量/不支持协议时使用 incoming。 if let (Some(Value::Object(old)), Value::Object(new)) = (&previous, &incoming) && (old == new || self.objects.assign_from(*old, *new)?) { @@ -1077,23 +1082,31 @@ impl Vm { Ok(incoming) } + /// Load 类操作把借用值放入拥有栈槽,先验证/retain 成功才压栈。 fn push_borrowed(&mut self, frame: &mut EndorFrame, value: Value) -> Result<(), VmError> { + // retain 失败时保持栈不变,避免产生没有拥有根的栈项。 self.retain_value(&value)?; frame.stack.push(StackEntry::Value(value)); Ok(()) } + /// 操作码完成后丢弃已消费输入,每个对象槽归还一根,Null 不参与计数。 fn discard(&mut self, frame: &mut EndorFrame, count: usize) { + // 逐项弹栈,代码验证保证数量足够,不按对象身份去重释放。 for _ in 0..count { + // Null 仅是调用协议标记,不能作为语言对象根释放。 if let StackEntry::Value(value) = frame.stack.pop().expect("verified stack height") { self.drop_root(value); } } } + /// Load/Store/DeleteDeref 与 LoadClosure 取对应 Cell;索引布局已验证,仍检查载荷种类。 fn cell_slot(&self, frame: &EndorFrame, index: usize) -> Result { + // 槽必须已初始化为 Cell 对象,Cell 内部仍可为空。 match &frame.localsplus[index] { Some(Value::Object(reference)) => { + // 验证具体 Cell 载荷,不仅检查 Object 变体。 self.payload::(*reference, "cell")?; Ok(*reference) } @@ -1104,7 +1117,9 @@ impl Vm { } } + /// ToBool/UnaryNot/条件跳转共用真值规则,直接值就地判断,对象交载荷协议。 fn value_is_true(&self, value: &Value) -> Result { + // 直接数值/字符串与对象载荷分开处理,协议错误不被当成 false。 match value { Value::Nil => Ok(false), Value::Bool(value) => Ok(*value), @@ -1114,7 +1129,9 @@ impl Vm { } } + /// 比较把直接 Text 与堆字符串统一成拥有 String,非字符串 None,坏句柄报错。 fn text_value(&self, value: &Value) -> Result, VmError> { + // 仅尝试所需具体载荷,非目标类别是正常 None,不是 kind mismatch。 Ok(match value { Value::Text(text) => Some(text.clone()), Value::Object(reference) => self @@ -1127,10 +1144,12 @@ impl Vm { }) } + /// 比较/算术将直接 Number 与堆数值统一为借用数值,不转换表示或新增根。 fn number_value<'a>( &'a self, value: &'a Value, ) -> Result, VmError> { + // 仅尝试所需具体载荷,非目标类别是正常 None,不是 kind mismatch。 Ok(match value { Value::Number(n) => Some(n), Value::Object(o) => self @@ -1143,16 +1162,20 @@ impl Vm { }) } + /// CompareOp 指令的语言比较入口,优先数学数值与字符串内容,然后对象协议/直接值相等。 fn compare_values( &self, left: &Value, right: &Value, operation: CompareOp, ) -> Result { + // 两侧都先尝试识别数值,不能对混合表示直接使用 Rust Eq。 let (left_number, right_number) = (self.number_value(left)?, self.number_value(right)?); + // 直接与堆数值都走数学比较,因此整数、小数、分数可跨表示等价。 if let (Some(left), Some(right)) = (left_number, right_number) { return left.numeric_compare(right, operation); } + // 仅一侧是数值时相等为 false、不等为 true,有序关系明确拒绝。 if left_number.is_some() || right_number.is_some() { return match operation { CompareOp::Eq => Ok(false), @@ -1160,6 +1183,7 @@ impl Vm { _ => Err(VmError::UnsupportedOperation("mixed numeric ordering")), }; } + // 两侧均为文本时按字符串内容比较,不做大小写折叠或 Unicode 规范化。 if let (Some(left), Some(right)) = (self.text_value(left)?, self.text_value(right)?) { return Ok(match operation { CompareOp::Eq => left == right, @@ -1170,9 +1194,11 @@ impl Vm { CompareOp::Ge => left >= right, }); } + // 非数值/文本的双对象走 Store 正向、反向与身份回退协议。 if let (Value::Object(left), Value::Object(right)) = (left, right) { return self.compare(*left, *right, operation); } + // 其他直接值只支持相等/不等,不能凭 Rust 变体顺序定义语言大小。 match operation { CompareOp::Eq => Ok(left == right), CompareOp::Ne => Ok(left != right), @@ -1180,17 +1206,25 @@ impl Vm { } } + /// BuildMap 逐对读取栈上键值,构造字典拥有根;失败释放半成品,输入栈由调用方清理。 fn build_map(&mut self, frame: &EndorFrame, count: usize) -> Result { + // 新字典先取得一份根,所有插入成功才移交给 BuildMap 调用方。 let dictionary = self.new_dict()?; + // 闭包收拢插入错误,外层统一释放未交付字典根。 let result = (|| -> Result<(), VmError> { + // 输入是末尾 count 对交错键值;代码验证已检查栈高及数量乘法。 let start = frame.stack.len() - count * 2; + // 按源码顺序插入,重复相等键遵循字典保留首次键位置的规则。 for index in 0..count { + // 先复制键的借用表示,原栈根继续保护其对象。 let key = Self::stack_value(frame, start + index * 2)?; + // 读取对应值的借用表示,字典提交后自己持有内部强边。 let value = Self::stack_value(frame, start + index * 2 + 1)?; self.dict_set(dictionary, key, value)?; } Ok(()) })(); + // 失败字典可能已持有部分键值边,释放其根会一并归还这些边。 if let Err(error) = result { self.release(dictionary)?; return Err(error); @@ -1198,45 +1232,58 @@ impl Vm { Ok(dictionary) } + /// MakeFunction 从默认值、Cell 和代码栈布局构造函数,成功返回拥有根,不先弹出输入。 fn make_function(&mut self, frame: &EndorFrame, argument: u32) -> Result { + // 低/高 16 位计数决定默认值段与 Cell 段,最后一项固定为代码。 let (default_count, closure_count) = Instruction::function_counts(argument); + // 代码必须是对象,后续 payload 再检查确为 EndorCodeObject。 let Value::Object(code) = Self::top_value(frame)? else { return Err(VmError::ObjectKindMismatch("code")); }; + // 复制代码诊断名,避免持载荷借用跨可变函数分配。 let name = self .payload::(code, "code")? .code() .name() .to_owned(); + // 栈高度由验证器证明,默认值段在 Cell 段之前。 let start = frame.stack.len() - default_count - closure_count - 1; + // 收集借用默认值表示,不提前归还栈根。 let defaults = (start..start + default_count) .map(|index| Self::stack_value(frame, index)) .collect::, _>>()?; + // 捕获段每项必须是对象;是否 Cell 以及数量是否吻合由函数构造再次验证。 let closure = (start + default_count..frame.stack.len() - 1) .map(|index| match Self::stack_value(frame, index)? { Value::Object(cell) => Ok(cell), _ => Err(VmError::ObjectKindMismatch("cell")), }) .collect::, _>>()?; - // Keep every input root on the stack until allocation has validated - // and retained all function edges. Failure leaves cleanup to the frame. + // 所有输入根在验证并建立函数内部强边前一直留栈,失败时仍由帧统一清理。 self.new_function(name, code, frame.globals, defaults, closure) } + /// BuildClass 从四个栈项构造类,保留当前函数作为字段注解环境,输入保活至构造完成。 fn build_class(&mut self, frame: &EndorFrame) -> Result { + // 类构造输入固定为名称、基类、字段字典、方法字典。 let start = frame.stack.len() - 4; + // 类名必须是直接文本,与元数据里显示名称分开。 let Value::Text(name) = Self::stack_value(frame, start)? else { return Err(VmError::ObjectKindMismatch("class name")); }; + // 基类必须是对象,Store 类构造进一步校验 Type 载荷与继承分配策略。 let Value::Object(base) = Self::stack_value(frame, start + 1)? else { return Err(VmError::ObjectKindMismatch("base Type")); }; + // 字段说明必须来自字典对象,读取时再校验具体载荷。 let Value::Object(fields) = Self::stack_value(frame, start + 2)? else { return Err(VmError::ObjectKindMismatch("field map")); }; + // 方法说明必须来自字典对象,函数句柄有效性由 Store 类构造验证。 let Value::Object(methods) = Self::stack_value(frame, start + 3)? else { return Err(VmError::ObjectKindMismatch("method map")); }; + // 字段键值都须为直接文本,形成按字典插入顺序排列的 ClassField。 let fields = self .dict_items(fields)? .iter() @@ -1248,6 +1295,7 @@ impl Vm { _ => Err(VmError::ObjectKindMismatch("class field annotation")), }) .collect::, _>>()?; + // 方法键须文本、值须对象,收集为名称有序表,仍不额外保留函数根。 let methods = self .dict_items(methods)? .iter() -- 2.52.0 From fb909de7fd25f5880c63165b88ebb8dcdbd948f4 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:56:37 +0800 Subject: [PATCH 24/62] docs(core): explain opcode execution and stack root transfers --- crates/core/src/eval.rs | 95 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 2 deletions(-) diff --git a/crates/core/src/eval.rs b/crates/core/src/eval.rs index f5ec9b8..24f43b2 100644 --- a/crates/core/src/eval.rs +++ b/crates/core/src/eval.rs @@ -1308,6 +1308,8 @@ impl Vm { .new_class_with_scope(name, base, fields, methods, Some(frame.function)) } + /// 所有同步/切片/协程入口共用的显式帧循环:Some 是拥有结果,None 是软预算或允许的 await 停放。 + /// 错误不在本函数销毁全部帧,由外层保存回溯再统一清理;指令索引/栈高已在代码构造时验证。 fn eval_frames( &mut self, frames: &mut Vec, @@ -1316,43 +1318,58 @@ impl Vm { slice_end: usize, allow_wait: bool, ) -> Result, VmError> { + // 每轮只推进当前最内层帧,源码调用通过帧向量增长而非 Rust 递归。 loop { + // 硬上限先于软边界检查,同点到达硬上限必须报错,不能继续 Pending。 if *executed >= limits.max_instructions { return Err(VmError::ExecutionLimit("instructions")); } + // 软量子结束保留帧、栈和指令指针,外层决定如何停放。 if *executed >= slice_end { return Ok(None); } + // 实际取指前累计一次,原生委托准备另在 prepare_call 计数。 *executed += 1; + // 当前帧必须存在;源码 Return 最外层时会提前返回结果退出循环。 let frame = frames.last_mut().expect("an evaluation frame is active"); + // 按完整逻辑指令起点取指,扩展前缀已合并,非法指针返回位置错误。 let decoded = frame.executable.instruction_at(frame.instruction_offset)?; + // 保存当前起点给错误诊断,同时预置顺序后继;跳转/等待会覆盖下一位置。 frame.current_offset = decoded.offset; frame.instruction_offset = decoded.next_offset; + // 本轮操作种类,后面的栈和槽更新按此分派。 let opcode = decoded.instruction.opcode; + // 完整 u32 操作数转为索引/数量,范围合法性由代码验证保证。 let argument = decoded.instruction.argument as usize; + // 每条指令成功后保持根计数与栈/槽一致,错误交外层清理当前帧。 match opcode { Opcode::Await => { + // 只读调用不允许 await,不可用等待期间外部修改破坏只读快照。 if self.objects.is_read_only() { return Err(VmError::UnsupportedOperation( "await during read-only execution", )); } + // 等待协议仅对象可实现,直接 Nil/数字/文本都不能 await。 let Value::Object(awaitable) = Self::top_value(frame)? else { return Err(VmError::ObjectKindMismatch("awaitable object")); }; + // poll_await 非阻塞观察当前状态,不重跑生成等待对象的表达式。 match self.object(awaitable)?.payload().poll_await()? { + // 操作数根继续留栈,协程状态稍后接管;同步入口不允许这种停放。 crate::AwaitPoll::Pending => { + // 仅可变协程支持 Pending await,同步调用不能悄悄变为暂停。 if !allow_wait { return Err(VmError::UnsupportedOperation( "pending await outside a coroutine", )); } - // Keep the evaluated operand, never replay its call - // or the statements before this await instruction. + // 保留已求值等待操作数,指针回到 Await 本身;不会重放此前调用或语句。 frame.waiting = true; frame.instruction_offset = decoded.offset; return Ok(None); } + // 先取得结果根再释放等待操作数,避免结果仅由等待对象图持有时失效。 crate::AwaitPoll::Ready(value) => { self.retain_value(&value)?; self.discard(frame, 1); @@ -1361,47 +1378,61 @@ impl Vm { } } } + // 无操作,只消耗本轮预算并继续预置后继。 Opcode::Nop => {} Opcode::LoadConst => { + // clone 仍为借用表示,push_borrowed 为结果栈项取得独立根。 let value = frame.executable.constants()[argument].clone(); self.push_borrowed(frame, value)?; } Opcode::LoadFast => { + // 未绑定 None 报具名局部错误,已绑定值压栈时另保留根。 let value = frame.localsplus[argument].clone().ok_or_else(|| { VmError::UnboundLocal(frame.executable.slot_name(argument).into()) })?; self.push_borrowed(frame, value)?; } + // 局部赋值先保留新槽根,再释放原栈输入及被替换旧槽根。 Opcode::StoreFast => { + // 先执行共享引用赋值选择,可能保留旧对象身份,再按目标槽/字典协议提交。 let value = self.assignment_value( frame.localsplus[argument].clone(), Self::top_value(frame)?, )?; self.retain_value(&value)?; self.discard(frame, 1); + // 新值先保留/写入,最后归还被替换的旧槽根,即使新旧同身份也安全。 if let Some(old) = frame.localsplus[argument].replace(value) { self.drop_root(old); } } Opcode::DeleteFast => { + // 先移走槽拥有值,使局部恢复 None,再归还旧根。 let value = frame.localsplus[argument].take().ok_or_else(|| { VmError::UnboundLocal(frame.executable.slot_name(argument).into()) })?; self.drop_root(value); } Opcode::LoadGlobal => { + // 从代码 names 表借用名称,索引不来自任意运行时文本。 let name = &frame.executable.layout().names[argument]; + // 按名记录命中/缺失,只在全局缺失时查询捕获的内置字典。 let mut value = self.objects.dict_get_name(frame.globals, name)?; + // Some(Nil) 也是命中,不会回退内置空间。 if value.is_none() && let Some(builtins) = frame.builtins { value = self.objects.dict_get_name(builtins, name)?; } + // 全局和可选内置均未命中才报 NameNotFound。 let value = value.ok_or_else(|| VmError::NameNotFound(name.clone()))?; self.push_borrowed(frame, value)?; } + // 全局修改由字典拥有新值内部边,成功后释放输入栈根。 Opcode::StoreGlobal => { + // 全局写入使用直接文本键,与名称读取协议对应。 let key = Value::Text(frame.executable.layout().names[argument].clone()); + // 先执行共享引用赋值选择,可能保留旧对象身份,再按目标槽/字典协议提交。 let value = self.assignment_value( self.dict_get(frame.globals, &key)?, Self::top_value(frame)?, @@ -1410,17 +1441,22 @@ impl Vm { self.discard(frame, 1); } Opcode::DeleteGlobal => { + // 从代码 names 表借用名称,索引不来自任意运行时文本。 let name = &frame.executable.layout().names[argument]; + // 删除缺键报告名称错误,字典删除协议其他错误正常传播。 if !self.dict_delete(frame.globals, &Value::Text(name.clone()))? { return Err(VmError::NameNotFound(name.clone())); } } Opcode::LoadDeref | Opcode::LoadClosure => { + // 对应槽必须存活且为 Cell,本帧与闭包函数均可能持有其根/边。 let cell = self.cell_slot(frame, argument)?; + // LoadClosure 压 Cell 本身,LoadDeref 压当前内容,两者都经 push_borrowed 取得栈根。 let value = if opcode == Opcode::LoadClosure { Value::Object(cell) } else { self.cell_get(cell).map_err(|error| { + // 语言层将空捕获槽映射为变量未绑定,其余底层错误保持原样。 if error == VmError::EmptyCell { VmError::UnboundLocal(frame.executable.slot_name(argument).into()) } else { @@ -1430,15 +1466,20 @@ impl Vm { }; self.push_borrowed(frame, value)?; } + // 空 Cell 读取经 ok() 视作没有旧值,写入成功后才弹出输入。 Opcode::StoreDeref => { + // 对应槽必须存活且为 Cell,本帧与闭包函数均可能持有其根/边。 let cell = self.cell_slot(frame, argument)?; + // 读取/计算下一状态后再更新栈,失败由原帧保留输入清理责任。 let value = self.assignment_value(self.cell_get(cell).ok(), Self::top_value(frame)?)?; self.cell_set(cell, value)?; self.discard(frame, 1); } Opcode::DeleteDeref => { + // 对应槽必须存活且为 Cell,本帧与闭包函数均可能持有其根/边。 let cell = self.cell_slot(frame, argument)?; + // 当前实现将读取错误视为未绑定;上方 cell_slot 已检查槽载荷。 if self.cell_get(cell).is_err() { return Err(VmError::UnboundLocal( frame.executable.slot_name(argument).into(), @@ -1446,41 +1487,56 @@ impl Vm { } self.cell_clear(cell)?; } + // 丢弃一个拥有栈项,协议 Null 则不 release。 Opcode::PopTop => self.discard(frame, 1), Opcode::Copy => { + // Copy 复制表示不自动 retain,对象项下方另取新栈根,Null 可直接复制。 let entry = frame.stack[frame.stack.len() - argument].clone(); + // 协议 Null 没有对象根;普通对象项复制时须取得一份新根。 if let StackEntry::Value(value) = &entry { self.retain_value(value)?; } frame.stack.push(entry); } Opcode::Swap => { + // Swap 仅交换现有拥有槽,不增加或释放任何根。 let length = frame.stack.len(); frame.stack.swap(length - 1, length - argument); } + // 压入专用调用标记,不能与 Value::Nil 混淆。 Opcode::PushNull => frame.stack.push(StackEntry::Null), Opcode::Call => { + // 调用布局是函数、receiver/Null、显式参数,argument 只计显式实参。 let start = frame.stack.len() - argument - 2; + // 调用目标必须为对象,是否可调用由 prepare_call/载荷协议继续判定。 let Value::Object(function) = Self::stack_value(frame, start)? else { return Err(VmError::UnsupportedOperation("call")); }; + // 先收集借用参数,原栈输入直到调用准备完成前保持根保护。 let mut arguments = Vec::with_capacity(argument + 1); + // 非 Null 的协议接收者先加入实际参数,Null 则不传入语言实参。 if let StackEntry::Value(value) = &frame.stack[start + 1] { arguments.push(value.clone()); } + // 后续显式参数按源码顺序传入,不在此处归还原栈根。 for index in start + 2..frame.stack.len() { arguments.push(Self::stack_value(frame, index)?); } + // 准备成功得到已持根源码帧或拥有结果,之后才能清理当前帧调用输入。 match self.prepare_call(function, arguments, executed, limits)? { + // 新帧还未加入向量,若深度预算不足必须主动清理它。 PreparedCall::Frame(mut callee) => { + // 硬帧限制包括现有所有源码帧,不把原生委托另算源码帧。 if frames.len() >= limits.max_frames { self.clear_frame(callee); return Err(VmError::ExecutionLimit("frames")); } + // 记录调用者索引,无裸指针,不受向量扩容影响。 callee.previous = Some(frames.len() - 1); self.discard(frames.last_mut().unwrap(), argument + 2); frames.push(callee); } + // 原生直接结果已经拥有根,清除调用输入后直接移入栈,不再 retain。 PreparedCall::Value(value) => { self.discard(frames.last_mut().unwrap(), argument + 2); frames @@ -1492,7 +1548,9 @@ impl Vm { } } Opcode::ReturnValue => { + // 返回值先借用读取,实际根在后面 pop 时从帧栈移交,不能提前 release。 let result = Self::top_value(frame)?; + // 源码返回注解已解析并保活,执行中全局重绑定不能替换该约束。 if let Some(expected) = frame.return_type && !self.objects.matches_annotation(&result, expected)? { @@ -1508,31 +1566,39 @@ impl Vm { actual: self.objects.value_type_name(&result)?, }); } + // 所有委托返回约束都要满足,按最内层到外层顺序检查。 for check in frame.native_returns.iter().rev() { check.check(&self.objects, &result)?; } + // 仅移出结果项不 release,将它原有的栈根转交调用者/外层结果。 frame.stack.pop(); self.clear_frame(frames.pop().unwrap()); + // 还有调用者则接管结果根到其栈;最外层直接移交给宿主。 if let Some(caller) = frames.last_mut() { caller.stack.push(StackEntry::Value(result)); } else { return Ok(Some(result)); } } + // 无条件跳转仅改变下一位置,不改变栈高度或引用计数。 Opcode::JumpForward | Opcode::JumpBackward => { frame.instruction_offset = decoded.jump_target()? } Opcode::PopJumpIfFalse | Opcode::PopJumpIfTrue => { + // 先求真再丢弃输入根,协议错误时输入仍在帧中供外层清理。 let truth = self.value_is_true(&Self::top_value(frame)?)?; self.discard(frame, 1); + // 条件成立才覆盖预置顺序后继,跳转距离以代码单元计算。 if truth == (opcode == Opcode::PopJumpIfTrue) { frame.instruction_offset = decoded.jump_target()?; } } Opcode::ToBool | Opcode::UnaryNot => { + // 先求真再丢弃输入根,协议错误时输入仍在帧中供外层清理。 let truth = self.value_is_true(&Self::top_value(frame)?)?; self.discard(frame, 1); frame.stack.push(StackEntry::Value(Value::Bool( + // ToBool 保留真值,UnaryNot 取反,两者都输出直接 Bool。 if opcode == Opcode::UnaryNot { !truth } else { @@ -1541,10 +1607,15 @@ impl Vm { ))); } Opcode::BinaryArithmetic => { + // 加法额外支持两个直接 Text 拼接,堆字符串不在此快速路径中。 if decoded.instruction.argument == 0 { + // 读取左输入借用表示,原栈根继续维持对象存活。 let left = Self::stack_value(frame, frame.stack.len() - 2)?; + // 读取右输入借用表示,不能先弹栈再访问对象载荷。 let right = Self::top_value(frame)?; + // 仅双直接文本走拼接,其他情况继续数值运算并校验 Number。 if let (Value::Text(a), Value::Text(b)) = (left, right) { + // 限制合并 UTF-8 字节数为 1 MiB,饱和相加防预算绕回。 if a.len().saturating_add(b.len()) > 1_048_576 { return Err(VmError::InvalidArgument( "text concatenation exceeds operation budget", @@ -1555,14 +1626,19 @@ impl Vm { continue; } } + // 读取左输入借用表示,原栈根继续维持对象存活。 let left = Self::stack_value(frame, frame.stack.len() - 2)?; + // 读取右输入借用表示,不能先弹栈再访问对象载荷。 let right = Self::top_value(frame)?; + // 直接/堆数值统一为借用 Number;缺失表示报 Number 种类错误。 let left = self .number_value(&left)? .ok_or(VmError::ObjectKindMismatch("Number"))?; + // 右值同样要求数值,不能隐式把文本或 Bool 转数字。 let right = self .number_value(&right)? .ok_or(VmError::ObjectKindMismatch("Number"))?; + // 操作数映射已由代码验证,运算仍可因零除/预算/非有限值报错。 let result = left.arithmetic( right, crate::ArithmeticOp::from_argument(decoded.instruction.argument).unwrap(), @@ -1571,17 +1647,23 @@ impl Vm { frame.stack.push(StackEntry::Value(Value::Number(result))); } Opcode::UnaryNegate => { + // 借用栈顶,原拥有根保留至协议运算或修改成功。 let value = Self::top_value(frame)?; + // 数值取负只接受直接/堆 Number,不丢弃其他类型信息做隐式转换。 let value = self .number_value(&value)? .ok_or(VmError::ObjectKindMismatch("Number"))?; + // 先成功计算再释放输入,输出直接 Number 自己拥有 Rust 数值数据。 let result = value.negated()?; self.discard(frame, 1); frame.stack.push(StackEntry::Value(Value::Number(result))); } Opcode::CompareOp => { + // 读取左输入借用表示,原栈根继续维持对象存活。 let left = Self::stack_value(frame, frame.stack.len() - 2)?; + // 读取右输入借用表示,不能先弹栈再访问对象载荷。 let right = Self::top_value(frame)?; + // 数学/文本/对象协议在辅助函数统一选择,结果是直接 Bool。 let result = self.compare_values( &left, &right, @@ -1591,23 +1673,28 @@ impl Vm { frame.stack.push(StackEntry::Value(Value::Bool(result))); } Opcode::LoadAttr => { + // 属性协议需要对象 owner,直接值不能假装有托管动态字段。 let Value::Object(owner) = Self::top_value(frame)? else { return Err(VmError::ObjectKindMismatch("object")); }; + // 读取/计算下一状态后再更新栈,失败由原帧保留输入清理责任。 let value = self.bind_attr(owner, &frame.executable.layout().names[argument])?; self.discard(frame, 1); frame.stack.push(StackEntry::Value(value)); } Opcode::BuildClass => { + // 构类成功返回拥有根,之后才丢弃名称/基类/字段/方法输入四项。 let class = self.build_class(frame)?; self.discard(frame, 4); frame.stack.push(StackEntry::Value(Value::Object(class))); } Opcode::StoreAttr => { + // 属性协议需要对象 owner,直接值不能假装有托管动态字段。 let Value::Object(owner) = Self::top_value(frame)? else { return Err(VmError::ObjectKindMismatch("object")); }; + // 读取/计算下一状态后再更新栈,失败由原帧保留输入清理责任。 let value = Self::stack_value(frame, frame.stack.len() - 2)?; self.set_attr( owner, @@ -1617,6 +1704,7 @@ impl Vm { self.discard(frame, 2); } Opcode::BuildMap => { + // 字典构造取得根后,释放原键值栈根,由字典内部边接管持有。 let dictionary = self.build_map(frame, argument)?; self.discard(frame, argument * 2); frame @@ -1624,12 +1712,15 @@ impl Vm { .push(StackEntry::Value(Value::Object(dictionary))); } Opcode::MakeFunction => { + // 函数构造校验并保留全部边后才消费原栈输入,避免捕获值提前释放。 let function = self.make_function(frame, decoded.instruction.argument)?; + // 按与构造器相同的位段计数弹出默认值、Cell 与代码项。 let (defaults, closure) = Instruction::function_counts(decoded.instruction.argument); self.discard(frame, defaults + closure + 1); frame.stack.push(StackEntry::Value(Value::Object(function))); } + // 扩展参数已由解码器合并,验证代码不含可执行的独立前缀。 Opcode::ExtendedArg => unreachable!("EXTENDED_ARG was decoded before evaluation"), } } -- 2.52.0 From fa29ad1f55f18e02ca2800c43a1ca6a975449b05 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:58:08 +0800 Subject: [PATCH 25/62] docs(core): explain builtin value and ownership regression scenarios --- crates/core/tests/builtins.rs | 90 +++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/crates/core/tests/builtins.rs b/crates/core/tests/builtins.rs index 5706f87..de46a70 100644 --- a/crates/core/tests/builtins.rs +++ b/crates/core/tests/builtins.rs @@ -1,20 +1,27 @@ +//! 测试框架调用的内置值/载荷集成回归,通过公开 Vm API 验证 objects 与执行器协议一致。 +//! 各场景区分 Rust 表示相等、语言数值/内容比较、外部拥有根和容器内部强边。 + use endor_core::{ CodeLayout, CompareOp, EndorBaseExceptionObject, EndorCode, EndorDictObject, EndorFunctionObject, EndorInteger, EndorNativeFunctionObject, EndorNumber, EndorNumberObject, EndorReal, EndorStringObject, EndorTracebackEntry, Instruction, Opcode, Value, Vm, VmError, }; +/// 测试数据辅助:构造直接 Text,不分配堆字符串对象。 fn text(value: &str) -> Value { Value::Text(value.into()) } #[test] +/// 验证整数规范化、任意长字面量保存、小数表示差异、分母符号和非法词法。 fn numbers_preserve_large_digits_decimal_scale_exponents_and_fraction_parts() { + // 超过 i64 的数字串仍可精确保存,只有显式 as_i64 收窄失败。 let huge = "123456789012345678901234567890123456789012345678901234567890"; assert_eq!(EndorNumber::integer(huge).unwrap().to_string(), huge); assert_eq!(EndorNumber::integer("-000").unwrap().to_string(), "0"); assert_eq!(EndorNumber::integer("+0012").unwrap().as_i64(), Some(12)); assert_eq!(EndorNumber::integer(huge).unwrap().as_i64(), None); + // 逐个验证合法小数原样保存,包括负零、尾零、边界小数点和超长指数。 for literal in [ "-0.0000", "1.2300000000000000000001", @@ -34,18 +41,24 @@ fn numbers_preserve_large_digits_decimal_scale_exponents_and_fraction_parts() { ); assert_eq!(EndorNumber::rational("0", "-6").unwrap().to_string(), "0/6"); assert!(EndorNumber::rational("1", "0").is_err()); + // 逐个拒绝非法词法,不忽略空白或把 Unicode 数字当 ASCII 数字。 for invalid in ["", "+", " 1", "1 ", "1_000", "12", "1.0"] { assert!(EndorNumber::integer(invalid).is_err(), "{invalid}"); } + // 逐个拒绝非法词法,不忽略空白或把 Unicode 数字当 ASCII 数字。 for invalid in ["", ".", "1.2.3", "1e", "1e+", "1e2e3", "nan", " 0.1", "--1"] { assert!(EndorNumber::decimal(invalid).is_err(), "{invalid}"); } } #[test] +/// 验证 IEEE 位保真、零真值、复数显示及未实现数值 hash/比较的明确拒绝。 fn number_objects_preserve_ieee_bits_and_reject_undefined_numeric_operations() { + // 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。 let mut vm = Vm::new().unwrap(); + // 带特定 NaN payload 的 IEEE 位,验证存储不会规范化掉低位信息。 let bits = 0x7ff8_0000_0000_0042; + // 显式 float 构造保留位,而非按数学等价改写。 let nan = EndorNumber::binary_float(f64::from_bits(bits)); assert_eq!(nan.as_real(), Some(&EndorReal::BinaryFloat(bits))); assert_ne!( @@ -53,10 +66,12 @@ fn number_objects_preserve_ieee_bits_and_reject_undefined_numeric_operations() { EndorNumber::binary_float(0.0) ); assert!(!EndorNumber::binary_float(f64::INFINITY).is_zero()); + // 超长指数的十进制负零仍为零,真值判断不需要进行指数运算。 let zero = vm .new_number(EndorNumber::decimal("-0.000e99999999999").unwrap()) .unwrap(); assert!(!vm.is_true(zero).unwrap()); + // 复数两分量可保留不同精确形式,但不代表复数算术/比较已经实现。 let complex = EndorNumber::complex( EndorReal::Integer(EndorInteger::parse("3").unwrap()), EndorNumber::rational("1", "3") @@ -65,6 +80,7 @@ fn number_objects_preserve_ieee_bits_and_reject_undefined_numeric_operations() { .unwrap() .clone(), ); + // 将复数放入堆载荷,验证通过 VM 协议访问的行为。 let object = vm.new_number(complex).unwrap(); assert_eq!(vm.str(object).unwrap(), "(3 + (1/3)i)"); assert_eq!( @@ -85,11 +101,17 @@ fn number_objects_preserve_ieee_bits_and_reject_undefined_numeric_operations() { } #[test] +/// 验证字符串按 Unicode 标量计数/索引,按内容相等及哈希,空串为假。 fn strings_use_unicode_characters_and_content_equality() { + // 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。 let mut vm = Vm::new().unwrap(); + // 两个分别分配的字符串身份不同,内容/哈希协议仍应相等。 let a = vm.new_string("Endor中文🦀").unwrap(); + // 相同内容的另一对象用于排除仅身份比较的错误实现。 let b = vm.new_string("Endor中文🦀").unwrap(); + // 空字符串真值应为假。 let empty = vm.new_string("").unwrap(); + // 借用具体载荷检查 Unicode 标量索引,不把 UTF-8 字节数当字符数。 let string = vm.payload::(a, "string").unwrap(); assert_eq!(string.char_count(), 8); assert_eq!(string.char_at(7), Some('🦀')); @@ -101,14 +123,21 @@ fn strings_use_unicode_characters_and_content_equality() { } #[test] +/// 验证堆字符串键与直接文本等价时保留首键及顺序,覆盖/删除正确释放旧边。 fn dictionaries_keep_order_and_first_key_and_own_keys_and_values() { + // 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。 let mut vm = Vm::new().unwrap(); + // 字典返回一份外部根;插入对象键值时另持内部边。 let dict = vm.new_dict().unwrap(); + // 堆字符串键之后与同内容直接 Text 比较,字典应保留此首键身份。 let key = vm.new_string("answer").unwrap(); + // 覆盖前的对象值,归还外部根后只由字典持有,便于检查覆盖释放。 let old = vm.new_object().unwrap(); vm.dict_set(dict, Value::Object(key), Value::Object(old)) .unwrap(); + // 归还测试根,后续键仍通过字典键边存活。 vm.release(key).unwrap(); + // 归还旧值测试根,字典覆盖时应释放其最后强边。 vm.release(old).unwrap(); vm.dict_set(dict, text("second"), Value::Nil).unwrap(); vm.dict_set(dict, text("answer"), text("replacement")) @@ -128,11 +157,17 @@ fn dictionaries_keep_order_and_first_key_and_own_keys_and_values() { } #[test] +/// 验证异堆/过期句柄、不可哈希键和错误容器种类不破坏既有值与计数。 fn failed_container_writes_leave_values_and_counts_unchanged() { + // 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。 let mut vm = Vm::new().unwrap(); + // 第二个 VM 提供异堆句柄,拒绝时不能把它保留在本堆容器。 let mut other = Vm::new().unwrap(); + // 异堆对象始终由其所属 VM 管理,不允许跨堆建立托管边。 let foreign = other.new_object().unwrap(); + // 字典返回一份外部根;插入对象键值时另持内部边。 let dict = vm.new_dict().unwrap(); + // 子对象根加上字典/Cell 内部边应按实际拥有次数计数。 let child = vm.new_object().unwrap(); vm.dict_set(dict, text("x"), Value::Object(child)).unwrap(); assert_eq!( @@ -152,13 +187,16 @@ fn failed_container_writes_leave_values_and_counts_unchanged() { vm.dict_set(dict, Value::Object(dict), Value::Nil), Err(VmError::UnsupportedOperation("dict hash")) ); + // Cell 创建即有一份外部根,初值对象额外成为内部边;None 则保持未初始化。 let cell = vm.new_cell(Some(Value::Object(child))).unwrap(); assert_eq!( vm.cell_set(cell, Value::Object(foreign)), Err(VmError::ForeignHandle) ); assert_eq!(vm.cell_get(cell).unwrap(), Value::Object(child)); + // 分配后立即释放以制造真正的过期身份,不能误认为普通缺值。 let dead = vm.new_object().unwrap(); + // 此后向 Cell 写入该身份必须报 InvalidHandle,不改变原值。 vm.release(dead).unwrap(); assert_eq!( vm.cell_set(cell, Value::Object(dead)), @@ -172,14 +210,19 @@ fn failed_container_writes_leave_values_and_counts_unchanged() { } #[test] +/// 验证 None 空槽与已存 Nil 不同,同对象重写不提前释放,clear 后旧值失效。 fn cells_share_rebinding_distinguish_empty_and_none_and_keep_stable_objects() { + // 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。 let mut vm = Vm::new().unwrap(); + // Cell 创建即有一份外部根,初值对象额外成为内部边;None 则保持未初始化。 let cell = vm.new_cell(None).unwrap(); assert_eq!(vm.cell_get(cell), Err(VmError::EmptyCell)); vm.cell_set(cell, Value::Nil).unwrap(); assert_eq!(vm.cell_get(cell).unwrap(), Value::Nil); + // 子对象根加上字典/Cell 内部边应按实际拥有次数计数。 let child = vm.new_object().unwrap(); vm.cell_set(cell, Value::Object(child)).unwrap(); + // 归还测试根后,容器/Cell 内部边仍须维持子对象存活。 vm.release(child).unwrap(); vm.cell_set(cell, Value::Object(child)).unwrap(); assert_eq!(vm.ref_count(child).unwrap(), 1); @@ -189,25 +232,36 @@ fn cells_share_rebinding_distinguish_empty_and_none_and_keep_stable_objects() { } #[test] +/// 验证同一 Cell 被字典多键持有要计多条边,外部根移除后互引环整体回收。 fn container_cycles_and_duplicate_edges_are_collected() { + // 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。 let mut vm = Vm::new().unwrap(); + // 字典返回一份外部根;插入对象键值时另持内部边。 let dict = vm.new_dict().unwrap(); + // Cell 创建即有一份外部根,初值对象额外成为内部边;None 则保持未初始化。 let cell = vm.new_cell(Some(Value::Object(dict))).unwrap(); vm.dict_set(dict, text("a"), Value::Object(cell)).unwrap(); vm.dict_set(dict, text("b"), Value::Object(cell)).unwrap(); assert_eq!(vm.ref_count(cell).unwrap(), 3); vm.release(dict).unwrap(); + // 归还 Cell 原根,闭包或字典边仍可持有它,最后无根环交给 GC。 vm.release(cell).unwrap(); assert_eq!(vm.collect_cycles(), 2); assert_eq!(vm.object_count(), 0); } #[test] +/// 验证代码常量、默认值、全局和共享捕获槽均有强边,闭包更新可见且无根环可回收。 fn functions_retain_globals_constants_defaults_and_shared_closure_cells() { + // 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。 let mut vm = Vm::new().unwrap(); + // 准备函数环境;本场景可故意使用非字典以测试种类校验。 let globals = vm.new_dict().unwrap(); + // 常量初始有测试根,代码常量表和默认值还会独立拥有强边。 let constant = vm.new_string("constant").unwrap(); + // Cell 创建即有一份外部根,初值对象额外成为内部边;None 则保持未初始化。 let cell = vm.new_cell(Some(text("first"))).unwrap(); + // 构造已验证代码元数据,只有分配代码载荷后才建立常量托管边。 let code = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadDeref, 1), @@ -222,7 +276,9 @@ fn functions_retain_globals_constants_defaults_and_shared_closure_cells() { }, ) .unwrap(); + // 托管代码对象有一份根,函数载荷还会额外持有代码边。 let code = vm.new_code(code).unwrap(); + // 函数/原生函数分配结果由测试持有根,载荷内部引用另计。 let function = vm .new_function( "read", @@ -232,15 +288,21 @@ fn functions_retain_globals_constants_defaults_and_shared_closure_cells() { vec![cell], ) .unwrap(); + // 第二个函数共用代码/全局/Cell,但默认值布局不同,拥有边各自计数。 let second = vm .new_function("read_again", code, globals, vec![], vec![cell]) .unwrap(); assert_eq!(vm.ref_count(constant).unwrap(), 3); + // 释放代码测试根后函数仍持有代码内部边,不能使执行数据常量失效。 vm.release(code).unwrap(); + // 常量原测试根归还,其余代码/默认值边仍保持存活。 vm.release(constant).unwrap(); + // 归还 Cell 原根,闭包或字典边仍可持有它,最后无根环交给 GC。 vm.release(cell).unwrap(); vm.cell_set(cell, text("updated")).unwrap(); + // 逐个读取两个函数捕获的同一 Cell,必须观察相同更新。 for reference in [function, second] { + // 借用函数载荷的闭包引用,不为读取额外保留根。 let f = vm .payload::(reference, "function") .unwrap(); @@ -256,15 +318,21 @@ fn functions_retain_globals_constants_defaults_and_shared_closure_cells() { } #[test] +/// 验证函数全局种类/闭包数量及代码重复槽名错误不新增根或持有边。 fn malformed_function_environments_are_rejected_without_retaining_any_edges() { + // 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。 let mut vm = Vm::new().unwrap(); + // 准备函数环境;本场景可故意使用非字典以测试种类校验。 let globals = vm.new_object().unwrap(); + // 最小装载常量并返回的逻辑指令,用于只改变环境或闭包布局的错误测试。 let instructions = [ Instruction::new(Opcode::LoadConst, 0), Instruction::simple(Opcode::ReturnValue), ]; + // 构造已验证代码元数据,只有分配代码载荷后才建立常量托管边。 let code = EndorCode::from_instructions(&instructions, vec![Value::Nil], CodeLayout::default()) .unwrap(); + // 托管代码对象有一份根,函数载荷还会额外持有代码边。 let code = vm.new_code(code).unwrap(); assert_eq!( vm.new_function("bad", code, globals, vec![], vec![]), @@ -273,6 +341,7 @@ fn malformed_function_environments_are_rejected_without_retaining_any_edges() { assert_eq!(vm.ref_count(globals).unwrap(), 1); assert_eq!(vm.ref_count(code).unwrap(), 1); assert!(EndorCode::new(vec![], vec![], vec!["x".into(), "x".into()], vec![]).is_err()); + // 构造已验证代码元数据,只有分配代码载荷后才建立常量托管边。 let code = EndorCode::from_instructions( &instructions, vec![Value::Nil], @@ -282,7 +351,9 @@ fn malformed_function_environments_are_rejected_without_retaining_any_edges() { }, ) .unwrap(); + // 托管代码对象有一份根,函数载荷还会额外持有代码边。 let code = vm.new_code(code).unwrap(); + // 准备函数环境;本场景可故意使用非字典以测试种类校验。 let globals = vm.new_dict().unwrap(); assert!( vm.new_function("bad", code, globals, vec![], vec![]) @@ -292,15 +363,20 @@ fn malformed_function_environments_are_rejected_without_retaining_any_edges() { } #[test] +/// 验证异常数据载荷的参数/链强边与注释/回溯元数据,错误 cause 种类被拒绝。 fn exceptions_preserve_arguments_chains_notes_and_traceback_without_type_classes() { + // 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。 let mut vm = Vm::new().unwrap(); + // 异常参数对象由 cause 的参数边持有,之后归还测试根仍须存活。 let argument = vm.new_string("detail").unwrap(); + // cause 异常载荷持有参数;主异常的 cause/context 两项都指向它。 let cause = vm .allocate(EndorBaseExceptionObject::new( "ValueError", vec![Value::Object(argument)], )) .unwrap(); + // 同一 cause 被两条字段边拥有,注释/回溯文本不构成托管对象边。 let exception = vm .allocate( EndorBaseExceptionObject::new("RuntimeError", vec![text("failed")]) @@ -315,8 +391,11 @@ fn exceptions_preserve_arguments_chains_notes_and_traceback_without_type_classes ) .unwrap(); assert_eq!(vm.ref_count(cause).unwrap(), 3); + // 归还参数外部根,链上异常仍通过参数边持有它。 vm.release(argument).unwrap(); + // 归还 cause 外部根,主异常两条链边继续保活。 vm.release(cause).unwrap(); + // 借用异常元数据观察;不在此处抛出异常或自动创建语言异常类。 let data = vm .payload::(exception, "exception") .unwrap(); @@ -324,8 +403,10 @@ fn exceptions_preserve_arguments_chains_notes_and_traceback_without_type_classes assert_eq!(data.notes(), &["while loading"]); assert_eq!(data.traceback()[0].line, 12); assert_eq!(vm.str(exception).unwrap(), "failed"); + // 归还主异常最后根会级联释放 cause 和参数,无需手动重复 release 内部边。 vm.release(exception).unwrap(); assert_eq!(vm.object_count(), 0); + // 字典不是异常,不能通过 with_cause 绕过内置校验。 let wrong = vm.new_dict().unwrap(); assert_eq!( vm.allocate(EndorBaseExceptionObject::new("Error", vec![]).with_cause(wrong)), @@ -333,7 +414,9 @@ fn exceptions_preserve_arguments_chains_notes_and_traceback_without_type_classes ); } +/// 旧式原生回调借用返回原参数;VM 必须为最终对象结果另取拥有根。 fn echo(arguments: &[Value]) -> Result { + // 回调只接受一个参数,clone 不增加计数,最终拥有根由 VM 原生调用路径取得。 match arguments { [value] => Ok(value.clone()), _ => Err(VmError::InvalidArgument("expected one value")), @@ -341,20 +424,27 @@ fn echo(arguments: &[Value]) -> Result { } #[test] +/// 验证原生传参不损失数值表示,即使原样返回对象参数也要独立结果根。 fn native_functions_pass_lossless_numbers_and_return_owned_objects() { + // 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。 let mut vm = Vm::new().unwrap(); + // 函数/原生函数分配结果由测试持有根,载荷内部引用另计。 let function = vm .allocate(EndorNativeFunctionObject::new("echo", echo)) .unwrap(); + // 精确小数作为直接参数,经原生回调传回仍保留全部数字和尾零。 let number = Value::Number(EndorNumber::decimal("12345678901234567890.000100").unwrap()); assert_eq!( vm.call(function, std::slice::from_ref(&number)).unwrap(), number ); + // 字典返回一份外部根;插入对象键值时另持内部边。 let dict = vm.new_dict().unwrap(); + // 调用结果与原参数身份相同,但对象结果必须额外有一份外部根。 let returned = vm.call(function, &[Value::Object(dict)]).unwrap(); assert_eq!(returned, Value::Object(dict)); assert_eq!(vm.ref_count(dict).unwrap(), 2); + // 只归还结果根,原 dict 根仍存在,计数应从二降为一。 vm.release_value(returned).unwrap(); assert_eq!(vm.ref_count(dict).unwrap(), 1); assert_eq!( -- 2.52.0 From 96c689d64dedf26e62dcadd6ed99f681c0747206 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:59:51 +0800 Subject: [PATCH 26/62] docs(core): explain runtime lifetime and payload protocol regressions --- crates/core/tests/runtime.rs | 138 +++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/crates/core/tests/runtime.rs b/crates/core/tests/runtime.rs index 8a38431..1d81305 100644 --- a/crates/core/tests/runtime.rs +++ b/crates/core/tests/runtime.rs @@ -1,3 +1,7 @@ +//! Rust 测试框架调用的 VM/Store 基础协议集成场景,不依赖源码编译器。 +//! Resource 用 Rust Drop 计数观察实际析构,Number/ReflectedOrdering 验证宿主载荷协议覆盖和反向分派。 +//! 每个测试独立建 VM;未显式归还的根及未收集环最终仍须由整个 VM 的销毁清理。 + use std::any::Any; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -6,31 +10,39 @@ use endor_core::{CompareOp, NativeFunction, ObjectPayload, ObjectRef, Record, Va #[derive(Debug)] struct Resource { + /// VM 外观察实际析构次数的共享计数器。 drops: Arc, + /// 分配时由 Heap 快照持有的内部强边,含重复项。 edges: Vec, } impl Drop for Resource { + /// Resource 真正销毁时累加共享计数,区分句柄复制/归还与实际 Rust 资源释放。 fn drop(&mut self) { self.drops.fetch_add(1, Ordering::SeqCst); } } impl ObjectPayload for Resource { + /// Store/比较协议的具体载荷下转型入口,不依赖语言名义类型。 fn as_any(&self) -> &dyn Any { self } + /// 错误与诊断显示的类别标签。 fn kind_name(&self) -> &'static str { "resource" } + /// Heap 分配时枚举载荷拥有边;重复项必须逐项计数,不能去重。 fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { + // 每个槽都是独立拥有边,不能因为身份相同而合并计数。 for reference in &self.edges { visit(*reference); } } + /// Resource 允许公共属性表,使测试能组合载荷边和属性边成环。 fn allows_attributes(&self) -> bool { true } @@ -40,34 +52,42 @@ impl ObjectPayload for Resource { struct Number(i64); impl ObjectPayload for Number { + /// Store/比较协议的具体载荷下转型入口,不依赖语言名义类型。 fn as_any(&self) -> &dyn Any { self } + /// 错误与诊断显示的类别标签。 fn kind_name(&self) -> &'static str { "number" } + /// 测试 Number 用值覆盖默认身份表示,供 str 默认回退调用。 fn repr(&self, _identity: ObjectRef) -> Result { Ok(self.0.to_string()) } + /// 测试 Number 仅零为假,不使用默认对象恒真规则。 fn is_true(&self) -> Result { Ok(self.0 != 0) } + /// 载荷自定义哈希协议;Number 按值,ReflectedOrdering 明确拒绝。 fn hash(&self, _identity: ObjectRef) -> Result { Ok(self.0 as u64) } + /// Store 先正向后反向调用;返回 None 是未实现,Some(false) 则不再回退。 fn compare( &self, other: &dyn ObjectPayload, operation: CompareOp, ) -> Result, VmError> { + // Number 只处理同类,其他载荷返回 None 允许 Store 继续分派。 let Some(other) = other.as_any().downcast_ref::() else { return Ok(None); }; + // 同类 Number 每个关系都返回明确结果,false 也不是未实现。 Ok(Some(match operation { CompareOp::Eq => self.0 == other.0, CompareOp::Ne => self.0 != other.0, @@ -79,6 +99,7 @@ impl ObjectPayload for Number { } } +/// 各生命周期测试共用分配器,保存计数器 Arc 与给定不可变边,返回一份拥有根。 fn resource(vm: &mut Vm, drops: &Arc, edges: Vec) -> ObjectRef { vm.allocate(Resource { drops: drops.clone(), @@ -88,15 +109,21 @@ fn resource(vm: &mut Vm, drops: &Arc, edges: Vec) -> Obj } #[test] +/// 验证新 VM 不隐式自举名义类型或内置对象;不代表 VM 没有显式类型 API。 fn starts_empty_without_language_classes_or_a_type_system() { + // 只观察初始空堆,不因查询自动初始化对象。 let vm = Vm::new().unwrap(); assert_eq!(vm.object_count(), 0); } #[test] +/// 验证普通 Record 的身份表示、恒真和身份相等回退,无序比较及不可调用的错误。 fn defaults_provide_identity_representation_hash_and_truth() { + // 建立独立堆,句柄不能跨测试/VM 使用。 let mut vm = Vm::new().unwrap(); + // 第一个独立对象/资源保留一份根,与 second/equal 比较身份或引用关系。 let first = vm.new_object().unwrap(); + // 另一个分配结果有独立身份,即使内容相同也不是同对象。 let second = vm.new_object().unwrap(); assert_eq!(vm.object(first).unwrap().header().identity(), first); assert!(vm.is_same(first, first).unwrap()); @@ -118,12 +145,18 @@ fn defaults_provide_identity_representation_hash_and_truth() { } #[test] +/// 验证复制句柄不保留对象,显式 retain/release 控制资源销毁与过期访问。 fn explicit_references_control_lifetime_and_copies_are_borrowed() { + // 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。 let drops = Arc::new(AtomicUsize::new(0)); + // 建立独立堆,句柄不能跨测试/VM 使用。 let mut vm = Vm::new().unwrap(); + // resource 返回一份外部根,后续复制不新增根。 let owned = resource(&mut vm, &drops, vec![]); + // 直接复制 ObjectRef,ref_count 仍为一,不能额外 release。 let borrowed = owned; assert_eq!(vm.ref_count(borrowed).unwrap(), 1); + // 显式多取得一份外部根,需两次归还后才实际析构。 vm.retain(owned).unwrap(); assert_eq!(vm.ref_count(owned).unwrap(), 2); vm.release(owned).unwrap(); @@ -136,13 +169,19 @@ fn explicit_references_control_lifetime_and_copies_are_borrowed() { } #[test] +/// 验证字段独立持有内部边,不能用外部 release 归还仅借用的属性边。 fn attributes_keep_objects_alive_and_delete_releases_them() { + // 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。 let drops = Arc::new(AtomicUsize::new(0)); + // 建立独立堆,句柄不能跨测试/VM 使用。 let mut vm = Vm::new().unwrap(); + // 普通 Record 字段作为子对象强边来源。 let owner = vm.new_object().unwrap(); + // 子对象先有分配根,写入属性后另有内部边。 let child = resource(&mut vm, &drops, vec![]); vm.set_attr(owner, "child", Value::Object(child)).unwrap(); assert_eq!(vm.ref_count(child).unwrap(), 2); + // 归还分配根后属性边继续保活,不能再当作拥有根重复 release。 vm.release(child).unwrap(); assert_eq!(vm.ref_count(child).unwrap(), 1); assert_eq!(vm.release(child), Err(VmError::NoOwnedReference)); @@ -156,11 +195,16 @@ fn attributes_keep_objects_alive_and_delete_releases_them() { } #[test] +/// 验证属性同对象重写先保留新边再归还旧边,随后改标量才释放最后强边。 fn replacing_the_same_edge_does_not_destroy_the_target() { + // 建立独立堆,句柄不能跨测试/VM 使用。 let mut vm = Vm::new().unwrap(); + // 普通 Record 字段作为子对象强边来源。 let owner = vm.new_object().unwrap(); + // 独立目标用于属性替换或多重载荷边计数。 let target = vm.new_object().unwrap(); vm.set_attr(owner, "child", Value::Object(target)).unwrap(); + // 归还原根,后续存活完全依赖已建立的内部边。 vm.release(target).unwrap(); vm.set_attr(owner, "child", Value::Object(target)).unwrap(); assert_eq!(vm.ref_count(target).unwrap(), 1); @@ -174,15 +218,22 @@ fn replacing_the_same_edge_does_not_destroy_the_target() { } #[test] +/// 验证宿主主动 retain 属性借用结果后,可在原父对象释放后继续使用。 fn borrowed_attribute_results_can_be_retained_by_the_host() { + // 建立独立堆,句柄不能跨测试/VM 使用。 let mut vm = Vm::new().unwrap(); + // 父对象拥有 child 字段边,释放父根会移除该边。 let parent = vm.new_object().unwrap(); + // 子对象先有分配根,写入属性后另有内部边。 let child = vm.new_object().unwrap(); vm.set_attr(parent, "child", Value::Object(child)).unwrap(); + // 归还分配根后属性边继续保活,不能再当作拥有根重复 release。 vm.release(child).unwrap(); + // get_attr 返回借用值,宿主若要跨父对象寿命使用须手动 retain。 let Value::Object(borrowed) = vm.get_attr(parent, "child").unwrap() else { panic!("expected an object"); }; + // 取得独立宿主根后,原持有字段消失也不会使结果失效。 vm.retain(borrowed).unwrap(); vm.release(parent).unwrap(); assert_eq!(vm.ref_count(borrowed).unwrap(), 1); @@ -191,12 +242,19 @@ fn borrowed_attribute_results_can_be_retained_by_the_host() { } #[test] +/// 验证异堆和过期值写入失败保持旧字段,retain/release/身份比较也验证堆归属。 fn failed_foreign_and_stale_writes_preserve_the_old_attribute() { + // 建立独立堆,句柄不能跨测试/VM 使用。 let mut vm = Vm::new().unwrap(); + // 第二个 VM 用于构造确实属于异堆的有效身份。 let mut other = Vm::new().unwrap(); + // 普通 Record 字段作为子对象强边来源。 let owner = vm.new_object().unwrap(); + // 句柄在所属 VM 中有效,但本 VM 不能 retain、存储或当作结果返回。 let foreign = other.new_object().unwrap(); + // 制造旧身份;释放后写入必须失败而非覆盖旧值。 let stale = vm.new_object().unwrap(); + // 销毁对象使句柄过期,后续不再拥有任何可归还的根。 vm.release(stale).unwrap(); vm.set_attr(owner, "value", Value::Number(1_i64.into())) .unwrap(); @@ -218,22 +276,32 @@ fn failed_foreign_and_stale_writes_preserve_the_old_attribute() { } #[test] +/// 验证槽代际与堆身份共同防止旧句柄别名新对象。 fn released_handles_cannot_alias_new_objects_or_new_vms() { + // 建立独立堆,句柄不能跨测试/VM 使用。 let mut vm = Vm::new().unwrap(); + // 释放后保留旧句柄表示,检查槽复用不会让它复活。 let old = vm.new_object().unwrap(); vm.release(old).unwrap(); + // 新分配可复用槽号,但代际必须不同。 let new = vm.new_object().unwrap(); assert_ne!(old, new); assert_eq!(vm.repr(old), Err(VmError::InvalidHandle)); + // 同号槽在另一堆也不是有效身份,必须报 ForeignHandle。 let other = Vm::new().unwrap(); assert!(matches!(other.object(new), Err(VmError::ForeignHandle))); } #[test] +/// 验证槽向量扩容不移动独立装箱对象实体。 fn boxed_objects_keep_their_addresses_when_the_slot_vector_grows() { + // 建立独立堆,句柄不能跨测试/VM 使用。 let mut vm = Vm::new().unwrap(); + // 第一个独立对象/资源保留一份根,与 second/equal 比较身份或引用关系。 let first = vm.new_object().unwrap(); + // 保存对象实体地址,不保存可能因向量扩容变化的槽地址。 let address = std::ptr::from_ref(vm.object(first).unwrap()); + // 压力设置:足够多次分配或链节点,检验稳定盒与非递归清理。 for _ in 0..4096 { vm.new_object().unwrap(); } @@ -241,25 +309,37 @@ fn boxed_objects_keep_their_addresses_when_the_slot_vector_grows() { } #[test] +/// 构造长单向拥有链,释放唯一头根应通过显式工作栈完成级联回收。 fn zero_reference_chains_are_released_without_recursive_destructors() { + // 建立独立堆,句柄不能跨测试/VM 使用。 let mut vm = Vm::new().unwrap(); + // 长链唯一保留外部根的起点。 let head = vm.new_object().unwrap(); + // tail 只是借用游标,后续节点仅由前节点 next 属性持有。 let mut tail = head; + // 压力设置:足够多次分配或链节点,检验稳定盒与非递归清理。 for _ in 0..4096 { + // 新节点先分配根,接上链后归还该根,仅留下内部边。 let next = vm.new_object().unwrap(); vm.set_attr(tail, "next", Value::Object(next)).unwrap(); vm.release(next).unwrap(); tail = next; } + // 唯一头根移除应触发整条无环链的级联释放。 vm.release(head).unwrap(); assert_eq!(vm.object_count(), 0); } #[test] +/// 验证有外部根的互引环保留,最后根归还后 collect_cycles 才整环回收。 fn cycles_remain_while_rooted_and_are_collected_when_unreachable() { + // 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。 let drops = Arc::new(AtomicUsize::new(0)); + // 建立独立堆,句柄不能跨测试/VM 使用。 let mut vm = Vm::new().unwrap(); + // 第一个独立对象/资源保留一份根,与 second/equal 比较身份或引用关系。 let first = resource(&mut vm, &drops, vec![]); + // 另一个分配结果有独立身份,即使内容相同也不是同对象。 let second = resource(&mut vm, &drops, vec![]); vm.set_attr(first, "next", Value::Object(second)).unwrap(); vm.set_attr(second, "next", Value::Object(first)).unwrap(); @@ -275,9 +355,13 @@ fn cycles_remain_while_rooted_and_are_collected_when_unreachable() { } #[test] +/// 验证回收垃圾环时只扣除其指向幸存者的边,不销毁仍有根的目标。 fn cycle_collection_preserves_rooted_targets_and_updates_counts() { + // 建立独立堆,句柄不能跨测试/VM 使用。 let mut vm = Vm::new().unwrap(); + // 幸存者保留外部根,即使垃圾环另持有它也不能被 GC 误删。 let live = vm.new_object().unwrap(); + // 自环使普通强计数无法归零;无外部根后交给显式环回收或 VM Drop。 let cycle = vm.new_object().unwrap(); vm.set_attr(cycle, "self", Value::Object(cycle)).unwrap(); vm.set_attr(cycle, "live", Value::Object(live)).unwrap(); @@ -290,12 +374,18 @@ fn cycle_collection_preserves_rooted_targets_and_updates_counts() { } #[test] +/// 验证 trace 两次报告同一目标就持有两条强边,释放载荷逐边归还。 fn payload_edges_are_owned_and_duplicate_edges_are_counted() { + // 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。 let drops = Arc::new(AtomicUsize::new(0)); + // 建立独立堆,句柄不能跨测试/VM 使用。 let mut vm = Vm::new().unwrap(); + // 独立目标用于属性替换或多重载荷边计数。 let target = vm.new_object().unwrap(); + // 载荷同时拥有同一目标两次,总强计数应包含两条内部边。 let holder = resource(&mut vm, &drops, vec![target, target]); assert_eq!(vm.ref_count(target).unwrap(), 3); + // 归还原根,后续存活完全依赖已建立的内部边。 vm.release(target).unwrap(); vm.release(holder).unwrap(); assert_eq!(vm.object_count(), 0); @@ -303,10 +393,15 @@ fn payload_edges_are_owned_and_duplicate_edges_are_counted() { } #[test] +/// 验证混合载荷边与属性边的环能被同一次图遍历回收。 fn payload_edges_participate_in_cycle_tracing() { + // 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。 let drops = Arc::new(AtomicUsize::new(0)); + // 建立独立堆,句柄不能跨测试/VM 使用。 let mut vm = Vm::new().unwrap(); + // 普通属性对象作为混合环的一端。 let record = vm.new_object().unwrap(); + // Resource 载荷拥有 record,record 的字段再反向拥有 Resource。 let native = resource(&mut vm, &drops, vec![record]); vm.set_attr(record, "native", Value::Object(native)) .unwrap(); @@ -317,11 +412,17 @@ fn payload_edges_participate_in_cycle_tracing() { } #[test] +/// 验证分配前完整边预检,后部异堆边失败不能部分保留前部有效边。 fn invalid_payload_edges_abort_allocation_without_retaining_valid_edges() { + // 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。 let drops = Arc::new(AtomicUsize::new(0)); + // 建立独立堆,句柄不能跨测试/VM 使用。 let mut vm = Vm::new().unwrap(); + // 第二个 VM 用于构造确实属于异堆的有效身份。 let mut other = Vm::new().unwrap(); + // 合法本堆边放在错误边前面,验证失败不部分提交其计数。 let local = vm.new_object().unwrap(); + // 句柄在所属 VM 中有效,但本 VM 不能 retain、存储或当作结果返回。 let foreign = other.new_object().unwrap(); assert_eq!( vm.allocate(Resource { @@ -336,10 +437,15 @@ fn invalid_payload_edges_abort_allocation_without_retaining_valid_edges() { } #[test] +/// 验证 Rust 载荷可覆盖显示、真值、哈希和比较,无需附着语言类型。 fn rust_payload_operations_override_defaults_without_language_types() { + // 建立独立堆,句柄不能跨测试/VM 使用。 let mut vm = Vm::new().unwrap(); + // 测试用 Number(0) 自定义真值为假,不是内置 EndorNumberObject。 let zero = vm.allocate(Number(0)).unwrap(); + // 第一个独立对象/资源保留一份根,与 second/equal 比较身份或引用关系。 let first = vm.allocate(Number(42)).unwrap(); + // 同值不同身份的 Number,必须内容相等且哈希一致。 let equal = vm.allocate(Number(42)).unwrap(); assert!(!vm.is_same(first, equal).unwrap()); assert!(vm.compare(first, equal, CompareOp::Eq).unwrap()); @@ -354,14 +460,17 @@ fn rust_payload_operations_override_defaults_without_language_types() { struct ReflectedOrdering; impl ObjectPayload for ReflectedOrdering { + /// Store/比较协议的具体载荷下转型入口,不依赖语言名义类型。 fn as_any(&self) -> &dyn Any { self } + /// 错误与诊断显示的类别标签。 fn kind_name(&self) -> &'static str { "reflected-ordering" } + /// Store 先正向后反向调用;返回 None 是未实现,Some(false) 则不再回退。 fn compare( &self, _other: &dyn ObjectPayload, @@ -370,23 +479,31 @@ impl ObjectPayload for ReflectedOrdering { Ok(Some(operation == CompareOp::Gt)) } + /// 载荷自定义哈希协议;Number 按值,ReflectedOrdering 明确拒绝。 fn hash(&self, _identity: ObjectRef) -> Result { Err(VmError::UnsupportedOperation("hash")) } } #[test] +/// 验证左侧未实现时把 Lt 反向为右侧 Gt,显式不支持 hash 原样传播。 fn reflected_operations_and_explicitly_unsupported_hashing_are_respected() { + // 建立独立堆,句柄不能跨测试/VM 使用。 let mut vm = Vm::new().unwrap(); + // 左侧 Record 不实现有序比较,Store 应尝试右侧反向协议。 let left = vm.allocate(Record).unwrap(); + // 右侧专门只对 Gt 返回真,证明左 Lt 正确反射。 let right = vm.allocate(ReflectedOrdering).unwrap(); assert!(vm.compare(left, right, CompareOp::Lt).unwrap()); assert_eq!(vm.hash(right), Err(VmError::UnsupportedOperation("hash"))); } +/// 简单旧式原生回调,仅接受两个直接整数并检查 i64 加法溢出;不是 Endor 通用算术实现。 fn add(arguments: &[Value]) -> Result { + // 先按参数形状匹配两个直接 Number,其他输入走回调参数错误。 match arguments { [Value::Number(left), Value::Number(right)] => { + // as_i64 同时成功才允许此测试回调运算,不接受大整数或小数隐式转换。 let (left, right) = left .as_i64() .zip(right.as_i64()) @@ -400,8 +517,11 @@ fn add(arguments: &[Value]) -> Result { } #[test] +/// 验证原生分派、回调错误、禁止动态属性,以及异堆参数在回调前拒绝。 fn native_calls_dispatch_validate_arguments_and_propagate_errors() { + // 建立独立堆,句柄不能跨测试/VM 使用。 let mut vm = Vm::new().unwrap(); + // 把 Rust add 包装为旧式原生函数,由 VM 负责参数/结果身份验证。 let function = vm.allocate(NativeFunction::new("add", add)).unwrap(); assert_eq!(vm.repr(function).unwrap(), ""); assert_eq!( @@ -427,7 +547,9 @@ fn native_calls_dispatch_validate_arguments_and_propagate_errors() { vm.set_attr(function, "x", Value::Nil), Err(VmError::UnsupportedOperation("attributes")) ); + // 第二个 VM 用于构造确实属于异堆的有效身份。 let mut other = Vm::new().unwrap(); + // 句柄在所属 VM 中有效,但本 VM 不能 retain、存储或当作结果返回。 let foreign = other.new_object().unwrap(); assert_eq!( vm.call(function, &[Value::Object(foreign)]), @@ -439,38 +561,54 @@ fn native_calls_dispatch_validate_arguments_and_propagate_errors() { struct ReturnsObject(ObjectRef); impl ObjectPayload for ReturnsObject { + /// Store/比较协议的具体载荷下转型入口,不依赖语言名义类型。 fn as_any(&self) -> &dyn Any { self } + /// 错误与诊断显示的类别标签。 fn kind_name(&self) -> &'static str { "invalid-return" } + /// 故意返回未经验证的原始对象句柄,测试 VM 能否在结果移交前阻止非法身份。 fn call(&self, _arguments: &[Value]) -> Result { Ok(Value::Object(self.0)) } } #[test] +/// 验证原生结果仍需堆归属/存活检查,宿主回调不能借返回通道塞入非法句柄。 fn native_results_cannot_smuggle_foreign_or_dead_handles_into_the_vm() { + // 建立独立堆,句柄不能跨测试/VM 使用。 let mut vm = Vm::new().unwrap(); + // 第二个 VM 用于构造确实属于异堆的有效身份。 let mut other = Vm::new().unwrap(); + // 句柄在所属 VM 中有效,但本 VM 不能 retain、存储或当作结果返回。 let foreign = other.new_object().unwrap(); + // 故意不 trace 持有的句柄,因为这里测试的是非法借用结果而非合法拥有边。 let invalid_return = vm.allocate(ReturnsObject(foreign)).unwrap(); assert_eq!(vm.call(invalid_return, &[]), Err(VmError::ForeignHandle)); + // 本堆对象释放后制造过期返回值,不与异堆错误混淆。 let dead = vm.new_object().unwrap(); + // 之后只剩失效句柄文本,回调返回不能把它重新保活。 vm.release(dead).unwrap(); + // 故意不 trace 持有的句柄,因为这里测试的是非法借用结果而非合法拥有边。 let invalid_return = vm.allocate(ReturnsObject(dead)).unwrap(); assert_eq!(vm.call(invalid_return, &[]), Err(VmError::InvalidHandle)); } #[test] +/// 验证离开 VM 作用域最终销毁仍有根对象及尚未显式 GC 的自环。 fn dropping_the_vm_cleans_up_rooted_objects_and_uncollected_cycles() { + // 共享 Rust 原子计数器独立于 VM 生命周期,测试可在对象销毁后读取实际析构次数。 let drops = Arc::new(AtomicUsize::new(0)); { + // 建立独立堆,句柄不能跨测试/VM 使用。 let mut vm = Vm::new().unwrap(); + // 离开局部 VM 作用域时仍有一份根,VM Drop 也必须释放宿主资源。 let rooted = resource(&mut vm, &drops, vec![]); + // 自环使普通强计数无法归零;无外部根后交给显式环回收或 VM Drop。 let cycle = resource(&mut vm, &drops, vec![]); vm.set_attr(cycle, "self", Value::Object(cycle)).unwrap(); vm.release(cycle).unwrap(); -- 2.52.0 From 4b93101e00decaf54e71ccf6d6bc4c6f3310a2a3 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 18:01:25 +0800 Subject: [PATCH 27/62] docs(core): explain nominal type and callable arity regressions --- crates/core/tests/types.rs | 108 ++++++++++++++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 2 deletions(-) diff --git a/crates/core/tests/types.rs b/crates/core/tests/types.rs index 8b91548..4655662 100644 --- a/crates/core/tests/types.rs +++ b/crates/core/tests/types.rs @@ -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 { Some(endor_core::CallArity::range(1, 2)) } + /// 数量检查通过后才设置 called 标志,返回直接 Nil,用于观测是否进入回调。 fn invoke( &self, cx: &mut endor_core::NativeContext<'_>, _: &[Value], ) -> Result { + // 回调唯一副作用,错误数量调用后的 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); } -- 2.52.0 From 72cdd04603f420cf24b2a00b39258e2b8ad491cb Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 18:02:50 +0800 Subject: [PATCH 28/62] docs(core): explain typed capability validation scenarios --- crates/core/tests/typed_traits.rs | 77 ++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/crates/core/tests/typed_traits.rs b/crates/core/tests/typed_traits.rs index 50ad5a2..23cb45d 100644 --- a/crates/core/tests/typed_traits.rs +++ b/crates/core/tests/typed_traits.rs @@ -1,3 +1,5 @@ +//! 完整类型化能力的集成回归:注册替换关系、调用实参/结果验证,以及能力定义的强边保活。 +//! 测试框架使用公开 Vm API,源码辅助函数手工构造已验证指令,无需源码解析器。 //! Remote-only coverage of explicit typed capability contracts. use endor_core::{ AnnotationType as T, CallEffect, CallSignature, CodeLayout, ContextNativeFunction, EndorCode, @@ -5,6 +7,7 @@ use endor_core::{ TypeConstructor, TypedMethodRequirement, Value, Vm, VmError, }; +/// 测试辅助:构造单个 check 方法能力,完整参数列表含显式 self,返回一份能力根。 fn capability( vm: &mut Vm, name: &str, @@ -21,14 +24,17 @@ fn capability( ) .unwrap() } +/// 测试辅助:构造先写 globals.ran=42 再返回给定常量的源码方法,附 owner 与类型注解。 fn source( vm: &mut Vm, + /// 含 Current 注解名称的字典,包装载荷通过 captures 持边。 globals: ObjectRef, owner: &str, types: &[&str], returns: &str, value: Value, ) -> ObjectRef { + // 手工代码提供可观察 ran 副作用,参数/局部布局按类型列表一一对应。 let code = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadConst, 0), @@ -51,30 +57,41 @@ fn source( method_owner: Some(owner.into()), }) .unwrap(); + // 分配代码后常量成为内部强边,函数将持有代码引用。 let code = vm.new_code(code).unwrap(); + // 分配源码/原生函数拥有根,完整签名与显式 captures 各自报告托管引用。 let function = vm .new_function("check", code, globals, vec![], vec![]) .unwrap(); + // 归还辅助函数临时代码根,源码函数仍拥有代码边。 vm.release(code).unwrap(); function } #[derive(Debug)] +/// 返回固定直接整数的测试原生分派器,签名由包装载荷快照持有。 struct Native(CallSignature); impl NativeCallable for Native { + /// 原生实现公布不可变完整签名,ContextNativeFunction 会快照并 trace 类型定义引用。 fn signature(&self) -> Option { Some(self.0.clone()) } + /// 原生测试入口;Native 返回直接整数,RebindThenReturn 先重绑定环境再返回借用对象。 fn invoke(&self, _: &mut NativeContext<'_>, _: &[Value]) -> Result { Ok(NativeResult::Return(Value::Number(7.into()))) } } #[test] +/// 注册失败不改变版本/声明;实现参数可比能力更宽,调用允许其自己签名支持的额外值。 fn typed_registration_is_atomic_and_wider_native_inputs_remain_callable() { + // 每个测试独立 VM,避免契约/全局修改污染其他场景。 let mut vm = Vm::new().unwrap(); + // Object/Type 自举根在末尾归还,元类型环随后显式回收。 let roots = vm.bootstrap_types().unwrap(); + // 名义类型用于方法 owner/签名;身份不是按类型名字符串比较。 let definition = vm .new_type("Model", roots.object, TypeConstructor::Record) .unwrap(); + // 能力签名供注册与实际调用核对,名义/能力引用由能力载荷 trace 保活。 let contract = capability( &mut vm, "Numeric", @@ -82,10 +99,12 @@ fn typed_registration_is_atomic_and_wider_native_inputs_remain_callable() { T::Number, CallEffect::ReadOnly, ); + // 分别测试参数缩窄与效果扩大,两者都必须在声明阶段失败。 for (argument, effect) in [ (T::String, CallEffect::ReadOnly), (T::Any, CallEffect::Mutating), ] { + // 分配源码/原生函数拥有根,完整签名与显式 captures 各自报告托管引用。 let function = vm .allocate(ContextNativeFunction::new( "bad", @@ -98,6 +117,7 @@ fn typed_registration_is_atomic_and_wider_native_inputs_remain_callable() { )) .unwrap(); vm.define_method(definition, "check", function).unwrap(); + // 失败能力声明不能修改类型载荷版本或留下已声明关系。 let version = vm.change_version(definition).unwrap(); assert!(matches!( vm.implement_trait(definition, contract), @@ -107,6 +127,7 @@ fn typed_registration_is_atomic_and_wider_native_inputs_remain_callable() { assert!(!vm.implements_trait(definition, contract).unwrap()); vm.release(function).unwrap(); } + // 分配源码/原生函数拥有根,完整签名与显式 captures 各自报告托管引用。 let function = vm .allocate(ContextNativeFunction::new( "good", @@ -120,6 +141,7 @@ fn typed_registration_is_atomic_and_wider_native_inputs_remain_callable() { .unwrap(); vm.define_method(definition, "check", function).unwrap(); vm.implement_trait(definition, contract).unwrap(); + // 实例先分配根再附着类型,作为显式 self 传入方法。 let receiver = vm.new_object().unwrap(); vm.attach_type(receiver, definition).unwrap(); assert_eq!( @@ -133,6 +155,7 @@ fn typed_registration_is_atomic_and_wider_native_inputs_remain_callable() { .unwrap(), Value::Number(7.into()) ); + // 逐项归还测试拥有根,类型/签名/捕获边由 Heap 自动维护,不能重复释放。 for object in [ receiver, function, @@ -143,18 +166,25 @@ fn typed_registration_is_atomic_and_wider_native_inputs_remain_callable() { ] { vm.release(object).unwrap(); } + // 收集无根元类型/签名/方法图环,断言最终没有存活堆对象。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] +/// 显式能力启用源码注解检查,参数错误应阻止函数体副作用,返回错误发生在体执行之后。 fn typed_source_methods_enforce_their_annotations_even_when_global_checks_are_off() { + // 每个测试独立 VM,避免契约/全局修改污染其他场景。 let mut vm = Vm::new().unwrap(); + // Object/Type 自举根在末尾归还,元类型环随后显式回收。 let roots = vm.bootstrap_types().unwrap(); + // 名义类型用于方法 owner/签名;身份不是按类型名字符串比较。 let definition = vm .new_type("Model", roots.object, TypeConstructor::Record) .unwrap(); + // 源码方法注解在此环境解析,同时 ran 标志观测函数体是否进入。 let globals = vm.new_dict().unwrap(); + // 源码参数声明 Number 但返回 Bool,分别验证参数阶段和返回阶段的检查。 let function = source( &mut vm, globals, @@ -164,14 +194,16 @@ fn typed_source_methods_enforce_their_annotations_even_when_global_checks_are_of Value::Bool(true), ); vm.define_method(definition, "check", function).unwrap(); + // 实例先分配根再附着类型,作为显式 self 传入方法。 let receiver = vm.new_object().unwrap(); vm.attach_type(receiver, definition).unwrap(); - // Existing untyped declarations retain the default-off compatibility mode. + // 尚未声明类型化能力且未开全局源码检查时,旧方法保持兼容:Bool 参数与结果都暂不验注解。 assert_eq!( vm.call(function, &[Value::Object(receiver), Value::Bool(false)]) .unwrap(), Value::Bool(true) ); + // 能力签名供注册与实际调用核对,名义/能力引用由能力载荷 trace 保活。 let contract = capability( &mut vm, "Numeric", @@ -180,6 +212,7 @@ fn typed_source_methods_enforce_their_annotations_even_when_global_checks_are_of CallEffect::ReadOnly, ); vm.implement_trait(definition, contract).unwrap(); + // 先复位 ran;类型不匹配参数应在任何源码 StoreGlobal 前被拒绝。 vm.dict_set(globals, Value::Text("ran".into()), Value::Number(0.into())) .unwrap(); assert!(matches!( @@ -197,6 +230,7 @@ fn typed_source_methods_enforce_their_annotations_even_when_global_checks_are_of vm.dict_get(globals, &Value::Text("ran".into())).unwrap(), Some(Value::Number(42.into())) ); + // 逐项归还测试拥有根,类型/签名/捕获边由 Heap 自动维护,不能重复释放。 for object in [ receiver, function, @@ -208,18 +242,25 @@ fn typed_source_methods_enforce_their_annotations_even_when_global_checks_are_of ] { vm.release(object).unwrap(); } + // 收集无根元类型/签名/方法图环,断言最终没有存活堆对象。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] +/// 能力签名的参数/返回名义定义被强边保留,能力销毁后无隐藏注册根阻止回收。 fn typed_trait_definitions_trace_their_signature_types_without_hidden_roots() { + // 每个测试独立 VM,避免契约/全局修改污染其他场景。 let mut vm = Vm::new().unwrap(); + // Object/Type 自举根在末尾归还,元类型环随后显式回收。 let roots = vm.bootstrap_types().unwrap(); + // 名义类型用于方法 owner/签名;身份不是按类型名字符串比较。 let definition = vm .new_type("Value", roots.object, TypeConstructor::Record) .unwrap(); + // 弱身份不持根,用于观察签名边是否真的保留定义及何时释放。 let weak = vm.downgrade(definition).unwrap(); + // 能力签名供注册与实际调用核对,名义/能力引用由能力载荷 trace 保活。 let contract = capability( &mut vm, "ValueContract", @@ -227,34 +268,48 @@ fn typed_trait_definitions_trace_their_signature_types_without_hidden_roots() { T::Nominal(definition), CallEffect::ReadOnly, ); + // 归还定义测试根,能力参数/返回签名的拥有边必须继续保活它。 vm.release(definition).unwrap(); + // 成功弱升级取得额外根,读完必须归还,否则会掩盖隐藏根检查。 let retained = vm .upgrade(weak) .unwrap() .expect("signature retains its type"); + // 仅归还弱升级的那一份根,签名仍持有自己的内部边。 vm.release(retained).unwrap(); + // 能力最后根归还后,其签名定义边也应消失。 vm.release(contract).unwrap(); + // 收集无根元类型/签名/方法图环,断言最终没有存活堆对象。 vm.collect_cycles(); assert!(vm.upgrade(weak).unwrap().is_none()); + // 逐项归还测试拥有根,类型/签名/捕获边由 Heap 自动维护,不能重复释放。 for object in [roots.object, roots.type_type] { vm.release(object).unwrap(); } + // 收集无根元类型/签名/方法图环,断言最终没有存活堆对象。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[derive(Debug)] struct RebindThenReturn { + /// 自身直接返回约束仍满足 Model,但外层能力返回约束需更严格重验 Feature。 signature: CallSignature, + /// 含 Current 注解名称的字典,包装载荷通过 captures 持边。 globals: ObjectRef, + /// 用于破坏 Current 约束的无关类型,captures 负责保活。 other: ObjectRef, + /// 返回的借用 Model 实例,结果根由 NativeContext 获取。 result: ObjectRef, } impl NativeCallable for RebindThenReturn { + /// 原生实现公布不可变完整签名,ContextNativeFunction 会快照并 trace 类型定义引用。 fn signature(&self) -> Option { Some(self.signature.clone()) } + /// 原生测试入口;Native 返回直接整数,RebindThenReturn 先重绑定环境再返回借用对象。 fn invoke(&self, cx: &mut NativeContext<'_>, _: &[Value]) -> Result { + // 函数体把 Current 重绑定为无关 Other,主动使返回 Model 的 Feature 契约失效。 cx.dict_set( self.globals, Value::Text("Current".into()), @@ -264,21 +319,29 @@ impl NativeCallable for RebindThenReturn { } } #[test] +/// 原生体重绑定返回对象的能力所需注解环境后,返回时必须重验并报错,重复失败不能残留递归标记。 fn actual_trait_returns_revalidate_capabilities_after_body_side_effects_and_restore_guards() { + // 每个测试独立 VM,避免契约/全局修改污染其他场景。 let mut vm = Vm::new().unwrap(); + // Object/Type 自举根在末尾归还,元类型环随后显式回收。 let roots = vm.bootstrap_types().unwrap(); + // Feature 方法返回值必须属于该基类型或其子类型。 let result_base = vm .new_type("Result", roots.object, TypeConstructor::Record) .unwrap(); + // Current 初始指向合法子类型,回调结束后的重验会观察该名称被替换。 let result_child = vm .new_type("ResultChild", result_base, TypeConstructor::Record) .unwrap(); + // 同为 Record 但不是 Result 子类,替换 Current 后应破坏 Feature 契约。 let other = vm .new_type("Other", roots.object, TypeConstructor::Record) .unwrap(); + // 返回值实例的类型,check 源码方法声明 Current 作为返回类型。 let model = vm .new_type("Model", roots.object, TypeConstructor::Record) .unwrap(); + // 源码方法注解在此环境解析,同时 ran 标志观测函数体是否进入。 let globals = vm.new_dict().unwrap(); vm.dict_set( globals, @@ -286,8 +349,10 @@ fn actual_trait_returns_revalidate_capabilities_after_body_side_effects_and_rest Value::Object(result_child), ) .unwrap(); + // 手工方法虽此处不运行,其当前签名仍用于能力符合性验证。 let read = source(&mut vm, globals, "Model", &["Model"], "Current", Value::Nil); vm.define_method(model, "check", read).unwrap(); + // Model 显式实现 Feature,要求 check 返回 Result 基类型。 let feature = capability( &mut vm, "Feature", @@ -296,11 +361,14 @@ fn actual_trait_returns_revalidate_capabilities_after_body_side_effects_and_rest CallEffect::External, ); vm.implement_trait(model, feature).unwrap(); + // 原生 produce 返回此实例;返回约束要求它仍满足 Feature 而非仅有历史声明。 let value = vm.new_object().unwrap(); vm.attach_type(value, model).unwrap(); + // 调用原生 produce 的接收者类型,与被返回 Model 分开。 let actor = vm .new_type("Actor", roots.object, TypeConstructor::Record) .unwrap(); + // 能力签名供注册与实际调用核对,名义/能力引用由能力载荷 trace 保活。 let contract = capability( &mut vm, "Producer", @@ -308,6 +376,7 @@ fn actual_trait_returns_revalidate_capabilities_after_body_side_effects_and_rest T::Capability(feature), CallEffect::External, ); + // 分配源码/原生函数拥有根,完整签名与显式 captures 各自报告托管引用。 let function = vm .allocate(ContextNativeFunction::new( "produce", @@ -326,13 +395,15 @@ fn actual_trait_returns_revalidate_capabilities_after_body_side_effects_and_rest .unwrap(); vm.define_method(actor, "check", function).unwrap(); vm.implement_trait(actor, contract).unwrap(); + // 实例先分配根再附着类型,作为显式 self 传入方法。 let receiver = vm.new_object().unwrap(); vm.attach_type(receiver, actor).unwrap(); assert!(matches!( vm.call(function, &[Value::Object(receiver)]), Err(VmError::TraitViolation { .. }) )); - // Repeated failures must not leave a recursive-validation guard marked active. + // 验证失败必须清理递归 guard;环境仍坏时每一次重新检查都应再次失败。 + // 重复能力失败验证 RAII 已清除递归 key,不能第二次被当重入跳过。 for _ in 0..2 { assert!(vm.implements_trait(model, feature).is_err()); } @@ -343,6 +414,7 @@ fn actual_trait_returns_revalidate_capabilities_after_body_side_effects_and_rest ) .unwrap(); assert!(vm.implements_trait(model, feature).unwrap()); + // 逐项归还测试拥有根,类型/签名/捕获边由 Heap 自动维护,不能重复释放。 for object in [ receiver, function, @@ -361,6 +433,7 @@ fn actual_trait_returns_revalidate_capabilities_after_body_side_effects_and_rest ] { vm.release(object).unwrap(); } + // 收集无根元类型/签名/方法图环,断言最终没有存活堆对象。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } -- 2.52.0 From 2cafa67f1e3cc0dffe2a84c9fb37b4105e074758 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 18:04:15 +0800 Subject: [PATCH 29/62] docs(core): explain native scopes delegation and tracked reads --- crates/core/tests/native_context.rs | 71 +++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/crates/core/tests/native_context.rs b/crates/core/tests/native_context.rs index afc7b78..9b344ab 100644 --- a/crates/core/tests/native_context.rs +++ b/crates/core/tests/native_context.rs @@ -1,3 +1,4 @@ +//! NativeContext 临时根、委托、观察依赖与完整签名的公开 API 回归;测试仍遵守远程执行约定。 //! Remote-only regression coverage. Do not execute locally. use endor_core::{ ChangeKey, ContextNativeFunction, ExecutionLimits, NativeCallable, NativeContext, NativeResult, @@ -5,12 +6,17 @@ use endor_core::{ }; #[derive(Debug)] +/// bool 控制是否故意失败,两个临时分配用于观察成功/失败清理差异。 struct Allocate(bool); impl NativeCallable for Allocate { + /// VM 在 NativeContext 内调用此测试分派器,临时分配与输入由上下文保活,返回/委托后移交输出根。 fn invoke(&self, cx: &mut NativeContext<'_>, _args: &[Value]) -> Result { + // 分配一份上下文临时根,成功返回时仅输出另取根移交,其余根由 Drop 归还。 let value = cx.new_object()?; cx.set_attr(value, "label", Value::Text("scoped".into()))?; + // 故意创建不返回对象,验证正常/报错都不会泄漏临时根。 let _temporary = cx.new_object()?; + // 故意在完成分配后报错,检验 RAII 不依赖回调正常返回。 if self.0 { return Err(VmError::InvalidArgument("intentional failure")); } @@ -19,8 +25,11 @@ impl NativeCallable for Allocate { } #[test] +/// 验证成功只移交一个结果根,未返回临时对象及错误路径分配全部回收。 fn scoped_allocations_return_one_root_and_clean_up_errors() { + // 独立 VM 隔离测试对象图,所有结果根由场景显式归还。 let mut vm = Vm::new().unwrap(); + // 分配上下文函数载荷,captures 管理显式托管边,分派器 Arc 不代替它们。 let function = vm .allocate(ContextNativeFunction::new( "allocate", @@ -28,12 +37,15 @@ fn scoped_allocations_return_one_root_and_clean_up_errors() { vec![], )) .unwrap(); + // VM 调用已完成根移交,返回对象应只有一份调用者根。 let value = vm.call(function, &[]).unwrap(); + // 解构返回值只复制身份,不增加引用计数。 let Value::Object(object) = value else { panic!("expected object") }; assert_eq!(vm.ref_count(object).unwrap(), 1); assert_eq!(vm.object_count(), 2); + // 归还成功结果,之后只剩函数载荷,临时对象应已自动清理。 vm.release_value(value).unwrap(); vm.replace_payload( function, @@ -47,8 +59,10 @@ fn scoped_allocations_return_one_root_and_clean_up_errors() { } #[derive(Debug)] +/// 返回目标借用身份,由包装载荷 captures 保活,NativeContext 为 Invoke 输出取得拥有根。 struct Delegate(ObjectRef); impl NativeCallable for Delegate { + /// VM 在 NativeContext 内调用此测试分派器,临时分配与输入由上下文保活,返回/委托后移交输出根。 fn invoke(&self, _cx: &mut NativeContext<'_>, args: &[Value]) -> Result { Ok(NativeResult::Invoke { function: self.0, @@ -58,8 +72,11 @@ impl NativeCallable for Delegate { } #[test] +/// 验证 intrinsic 检查读取当前载荷的真实 Rust 实现,不能仅按不变函数身份推断。 fn native_implementation_inspection_follows_payload_replacement() { + // 独立 VM 隔离测试对象图,所有结果根由场景显式归还。 let mut vm = Vm::new().unwrap(); + // 分配上下文函数载荷,captures 管理显式托管边,分派器 Arc 不代替它们。 let function = vm .allocate(ContextNativeFunction::new( "allocate", @@ -67,12 +84,15 @@ fn native_implementation_inspection_follows_payload_replacement() { vec![], )) .unwrap(); + // 借用当前 ContextNativeFunction 载荷,下转型检查不分配对象或执行回调。 let payload = vm .payload::(function, "native") .unwrap(); assert!(payload.implementation::().is_some()); assert!(payload.implementation::().is_none()); + // 记录更新前版本,替换实现后身份不变但版本必须增加。 let version = vm.change_version(function).unwrap(); + // 委托目标有测试根,也会由 Delegate 包装载荷显式 captures 保留。 let target = vm .allocate(ContextNativeFunction::new( "target", @@ -85,12 +105,14 @@ fn native_implementation_inspection_follows_payload_replacement() { ContextNativeFunction::new("delegate", Delegate(target), vec![target]), ) .unwrap(); + // 借用当前 ContextNativeFunction 载荷,下转型检查不分配对象或执行回调。 let payload = vm .payload::(function, "native") .unwrap(); assert!(payload.implementation::().is_none()); assert_eq!(payload.implementation::().unwrap().0, target); assert!(vm.change_version(function).unwrap() > version); + // 逐项归还测试拥有根,包装载荷内部捕获边由 Heap 自动释放。 for object in [function, target] { vm.release(object).unwrap(); } @@ -98,8 +120,11 @@ fn native_implementation_inspection_follows_payload_replacement() { } #[test] +/// 验证原生委托与目标共享累计预算,预算失败后目标/参数临时根计数恢复。 fn native_delegation_shares_budget_and_cleans_owned_arguments() { + // 独立 VM 隔离测试对象图,所有结果根由场景显式归还。 let mut vm = Vm::new().unwrap(); + // 委托目标有测试根,也会由 Delegate 包装载荷显式 captures 保留。 let target = vm .allocate(ContextNativeFunction::new( "target", @@ -107,6 +132,7 @@ fn native_delegation_shares_budget_and_cleans_owned_arguments() { vec![], )) .unwrap(); + // Delegate 返回 Invoke 而不是递归 vm.call,让外层共享预算与清理流程。 let delegate = vm .allocate(ContextNativeFunction::new( "delegate", @@ -114,6 +140,7 @@ fn native_delegation_shares_budget_and_cleans_owned_arguments() { vec![target], )) .unwrap(); + // 额外对象参数用于观察失败委托是否遗留执行根。 let arg = vm.new_object().unwrap(); assert!( vm.call_with_limits( @@ -128,7 +155,9 @@ fn native_delegation_shares_budget_and_cleans_owned_arguments() { ); assert_eq!(vm.ref_count(arg).unwrap(), 1); assert_eq!(vm.ref_count(target).unwrap(), 2); + // VM 调用已完成根移交,返回对象应只有一份调用者根。 let value = vm.call(delegate, &[]).unwrap(); + // 归还成功结果,之后只剩函数载荷,临时对象应已自动清理。 vm.release_value(value).unwrap(); vm.release(delegate).unwrap(); vm.release(target).unwrap(); @@ -137,32 +166,45 @@ fn native_delegation_shares_budget_and_cleans_owned_arguments() { } #[test] +/// 弱句柄不持根;升级产生根,原对象释放后槽复用也不复活旧弱身份。 fn weak_references_expire_without_retaining_reused_slots() { + // 独立 VM 隔离测试对象图,所有结果根由场景显式归还。 let mut vm = Vm::new().unwrap(); + // 原始对象有一份分配根,弱身份或通知记录都不应额外持有它。 let object = vm.new_object().unwrap(); + // downgrade 只形成借用弱身份,不延长对象寿命。 let weak = vm.downgrade(object).unwrap(); + // 成功升级才取得一份独立外部根,随后必须归还。 let upgraded = vm.upgrade(weak).unwrap().unwrap(); vm.release(upgraded).unwrap(); vm.release(object).unwrap(); + // 新对象可能复用同槽,但代际不同不能复活 weak。 let replacement = vm.new_object().unwrap(); assert!(vm.upgrade(weak).unwrap().is_none()); + // 另一堆升级旧身份应明确报 ForeignHandle,而不是过期 None。 let mut foreign = Vm::new().unwrap(); assert_eq!(foreign.upgrade(weak), Err(VmError::ForeignHandle)); vm.release(replacement).unwrap(); } #[test] +/// 同对象/字段通知合并最新版本,容器更新发 Contents,通知不充当 GC 根。 fn changes_coalesce_cover_containers_and_do_not_keep_sources_alive() { + // 独立 VM 隔离测试对象图,所有结果根由场景显式归还。 let mut vm = Vm::new().unwrap(); + // 原始对象有一份分配根,弱身份或通知记录都不应额外持有它。 let object = vm.new_object().unwrap(); vm.set_attr(object, "value", Value::Bool(false)).unwrap(); vm.set_attr(object, "value", Value::Bool(true)).unwrap(); + // 两次同名写入的最终版本应出现在合并通知中。 let version = vm.change_version(object).unwrap(); + // 取走通知批次同时重置缓存,数据中的源身份不 retain。 let changes = vm.take_changes(); assert!(!changes.rescan_required); assert_eq!(changes.changes.len(), 1); assert_eq!(changes.changes[0].key, ChangeKey::Attribute("value".into())); assert_eq!(changes.changes[0].version, version); + // Cell 更新/清空使用载荷替换,通知键为 Contents,不是普通字段。 let cell = vm.new_cell(None).unwrap(); vm.cell_set(cell, Value::Object(object)).unwrap(); vm.cell_clear(cell).unwrap(); @@ -175,12 +217,17 @@ fn changes_coalesce_cover_containers_and_do_not_keep_sources_alive() { } #[test] +/// 超过通知唯一键上限清空精确条目并标记重扫,取走批次后新通知正常开始。 fn notification_overflow_requires_rescan_instead_of_losing_changes_silently() { + // 独立 VM 隔离测试对象图,所有结果根由场景显式归还。 let mut vm = Vm::new().unwrap(); + // 原始对象有一份分配根,弱身份或通知记录都不应额外持有它。 let object = vm.new_object().unwrap(); + // 制造超过 4096 个不同字段键,重复同键不足以触发通知溢出。 for index in 0..4100 { vm.set_attr(object, index.to_string(), Value::Nil).unwrap(); } + // 取走通知批次同时重置缓存,数据中的源身份不 retain。 let changes = vm.take_changes(); assert!(changes.rescan_required); assert!(changes.changes.is_empty()); @@ -191,15 +238,21 @@ fn notification_overflow_requires_rescan_instead_of_losing_changes_silently() { #[derive(Debug)] struct ReadResult { + /// 输入借用身份,包装载荷 captures 负责保活。 source: ObjectRef, + /// 是否故意写旧输入,触发只读保护错误。 mutate: bool, } impl NativeCallable for ReadResult { + /// VM 在 NativeContext 内调用此测试分派器,临时分配与输入由上下文保活,返回/委托后移交输出根。 fn invoke(&self, cx: &mut NativeContext<'_>, _: &[Value]) -> Result { + // 借用读取 source 的输入字段,读取路径纳入当前只读依赖收集。 let value = cx.store().get_attr(self.source, "value")?; + // 非法分支试图修改进入只读前已存在的 source,应在写屏障处失败。 if self.mutate { cx.set_attr(self.source, "value", Value::Number(99_i64.into()))?; } + // 新结果不在进入只读时的保护快照内,允许初始化 copied 字段。 let result = cx.new_object()?; cx.set_attr(result, "copied", value)?; Ok(NativeResult::Return(Value::Object(result))) @@ -207,11 +260,15 @@ impl NativeCallable for ReadResult { } #[test] +/// 只读调用记录输入观察并允许构造新结果,非法改旧对象报错后作用域必须恢复。 fn read_only_calls_track_borrowed_dependencies_allow_results_and_restore_after_error() { + // 独立 VM 隔离测试对象图,所有结果根由场景显式归还。 let mut vm = Vm::new().unwrap(); + // 输入源在调用前创建并初始化,是只读保护与读取依赖的对象。 let source = vm.new_object().unwrap(); vm.set_attr(source, "value", Value::Number(7_i64.into())) .unwrap(); + // 分配上下文函数载荷,captures 管理显式托管边,分派器 Arc 不代替它们。 let function = vm .allocate(ContextNativeFunction::new( "read result", @@ -222,12 +279,15 @@ fn read_only_calls_track_borrowed_dependencies_allow_results_and_restore_after_e vec![source], )) .unwrap(); + // 丢弃场景初始化通知,只检查本次只读派生构造是否发布了输入变更。 vm.take_changes(); + // 结果是拥有值,reads 是借用观察集合,两个所有权语义不能混同。 let (value, reads) = vm .call_read_only_with_limits(function, &[], ExecutionLimits::default()) .unwrap(); assert!(reads.contains(&source)); assert!(vm.take_changes().changes.is_empty()); + // 检查新结果 copied 值后归还结果根,不能 release 读取依赖句柄。 let Value::Object(result) = value else { panic!("result") }; @@ -264,40 +324,51 @@ fn read_only_calls_track_borrowed_dependencies_allow_results_and_restore_after_e } #[test] +/// 注解验证也记录依赖,但单独标为非内容读取;同对象普通读取随后提升为内容读取。 fn annotation_reads_remain_dependencies_without_becoming_program_content_reads() { use endor_core::AnnotationType; + // 本测试直接使用 Store 精确分类入口,不需要执行器帧。 let mut store = objects::ObjectStore::new().unwrap(); + // 普通 Record 不符合 Number,但有效对象应符合 Any。 let object = store.new_object().unwrap(); store .set_attr(object, "value", Value::Number(1.into())) .unwrap(); + // 只读范围收集整体观察,子区间分类不会清空外层依赖。 let scope = store.begin_read_only(); + // 每个子区间重新开始收集,按 LIFO 结束;重复读取也要在新区域出现。 let token = store.begin_read_tracking().unwrap(); assert!( store .matches_annotation(&Value::Object(object), AnnotationType::Any) .unwrap() ); + // 返回 ReadAccess 区分 content_read,句柄仍不拥有根。 let reads = store.end_read_tracking_details(token).unwrap(); assert!( reads .iter() .any(|read| read.object == object && !read.content_read) ); + // 每个子区间重新开始收集,按 LIFO 结束;重复读取也要在新区域出现。 let token = store.begin_read_tracking().unwrap(); assert!( !store .matches_annotation(&Value::Object(object), AnnotationType::Number) .unwrap() ); + // 异堆对象即使匹配 Any 也先拒绝无效归属。 let mut foreign = objects::ObjectStore::new().unwrap(); + // 有效但异堆的测试输入,用于排除 Any 跳过句柄校验的错误实现。 let alien = foreign.new_object().unwrap(); assert!(matches!( store.matches_annotation(&Value::Object(alien), AnnotationType::Any), Err(VmError::ForeignHandle) )); foreign.release(alien).unwrap(); + // 补一次普通字段读取,与前面的验证读取合并时 content_read 必须为 true。 store.get_attr(object, "value").unwrap(); + // 返回 ReadAccess 区分 content_read,句柄仍不拥有根。 let reads = store.end_read_tracking_details(token).unwrap(); assert!( reads -- 2.52.0 From 91c611447a44cdb3422c8de5f161faf420cc95a9 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 18:05:36 +0800 Subject: [PATCH 30/62] docs(core): explain native signature and delegated result regressions --- crates/core/tests/native_context.rs | 87 ++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/crates/core/tests/native_context.rs b/crates/core/tests/native_context.rs index 9b344ab..09d77f6 100644 --- a/crates/core/tests/native_context.rs +++ b/crates/core/tests/native_context.rs @@ -382,23 +382,32 @@ fn annotation_reads_remain_dependencies_without_becoming_program_content_reads() #[derive(Debug)] struct TypedNative { + /// 构造时快照的完整签名,包装载荷负责 trace 类型定义。 signature: endor_core::CallSignature, + /// VM 外共享调用计数,clone Arc 不涉及托管对象根。 calls: std::sync::Arc, + /// 可选委托目标借用身份,包装载荷 captures 必须持有对应强边。 delegate: Option, + /// 无委托时是否分配新对象,否则回传最后参数/Nil。 allocate_result: bool, } impl NativeCallable for TypedNative { + /// ContextNativeFunction 快照此完整元数据并 trace 定义边;clone 本身不增加外部根。 fn signature(&self) -> Option { Some(self.signature.clone()) } + /// 测试原生分派入口,回调内借用参数/目标,NativeContext 校验并移交 Return 或 Invoke 输出根。 fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result { + // 计数器只在真实回调进入后增加,测试据此区分预检失败与返回后失败。 self.calls.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + // 委托不递归执行 VM,返回目标/参数借用表示给统一准备循环。 if let Some(function) = self.delegate { return Ok(NativeResult::Invoke { function, arguments: args.to_vec(), }); } + // 可选择产生新对象或回传最后参数,分别检验临时根清理和借用结果移交。 Ok(NativeResult::Return(if self.allocate_result { Value::Object(cx.new_object()?) } else { @@ -408,14 +417,18 @@ impl NativeCallable for TypedNative { } #[test] +/// 参数错误必须在 calls 增加前拒绝,错误对象结果由上下文回收,不留下临时分配。 fn native_types_preflight_arguments_and_clean_wrong_result_roots() { use endor_core::{AnnotationType as T, CallEffect, CallSignature}; use std::sync::{ Arc, atomic::{AtomicUsize, Ordering}, }; + // 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。 let mut vm = Vm::new().unwrap(); + // VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。 let calls = Arc::new(AtomicUsize::new(0)); + // 分配上下文载荷,签名类型与显式 captures 各自通过 trace 保活。 let function = vm .allocate(ContextNativeFunction::new( "typed", @@ -442,12 +455,15 @@ fn native_types_preflight_arguments_and_clean_wrong_result_roots() { } #[test] +/// 多层原生包装委托同一源码帧,最终结果必须满足链上全部返回约束,不额外消耗源码帧深度。 fn delegated_source_results_obey_every_native_contract_without_extra_frames() { use endor_core::{ AnnotationType as T, CallEffect, CallSignature, CodeLayout, EndorCode, Instruction, Opcode, }; use std::sync::{Arc, atomic::AtomicUsize}; + // 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。 let mut vm = Vm::new().unwrap(); + // 最小源码函数返回固定常量,区分原生包装约束与源码帧自身的执行。 let code = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadConst, 0), @@ -457,12 +473,17 @@ fn delegated_source_results_obey_every_native_contract_without_extra_frames() { CodeLayout::default(), ) .unwrap(); + // 托管代码拥有常量强边,源码函数还会持有代码边。 let code = vm.new_code(code).unwrap(); + // 源码环境有分配根,函数持有它的内部边。 let globals = vm.new_dict().unwrap(); + // 源码函数返回文本或 Nil,作为后续返回契约一致/不一致的终点。 let source = vm .new_function("source", code, globals, vec![], vec![]) .unwrap(); + // VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。 let calls = Arc::new(AtomicUsize::new(0)); + // 内层包装允许 String,与源码文本结果相符。 let inner = vm .allocate(ContextNativeFunction::new( "inner", @@ -475,6 +496,7 @@ fn delegated_source_results_obey_every_native_contract_without_extra_frames() { vec![source], )) .unwrap(); + // 外层却要求 Number,必须在源码最终返回时拒绝,即使内层已通过。 let outer = vm .allocate(ContextNativeFunction::new( "outer", @@ -487,6 +509,7 @@ fn delegated_source_results_obey_every_native_contract_without_extra_frames() { vec![inner], )) .unwrap(); + // 只允许一层源码帧,验证多层原生委托不会伪造额外源码帧。 let limits = ExecutionLimits { max_frames: 1, max_instructions: 100, @@ -509,26 +532,34 @@ fn delegated_source_results_obey_every_native_contract_without_extra_frames() { ), Err(VmError::ExecutionLimit("frames")) )); + // 归还测试每份拥有根,签名/捕获/方法内部边由 Heap 处理。 for object in [outer, inner, source, globals, code] { vm.release(object).unwrap(); } + // 清理可能由类型/签名/方法持有关系形成的无根环,断言堆归零。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] +/// 签名独立 trace 名义类型定义,绑定后仍使用真实目标签名并扣除 self。 fn signatures_trace_nominal_types_and_bound_receivers_use_the_actual_contract() { use endor_core::{AnnotationType as T, CallEffect, CallSignature, TypeConstructor}; use std::sync::{ Arc, atomic::{AtomicUsize, Ordering}, }; + // 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。 let mut vm = Vm::new().unwrap(); + // 显式自举 Object/Type,名义定义在同一对象图中受计数管理。 let roots = vm.bootstrap_types().unwrap(); + // 名义定义用于签名约束,归还测试根后仍需由签名边保活。 let definition = vm .new_type("Receiver", roots.object, TypeConstructor::Record) .unwrap(); + // VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。 let calls = Arc::new(AtomicUsize::new(0)); + // 分配上下文载荷,签名类型与显式 captures 各自通过 trace 保活。 let function = vm .allocate(ContextNativeFunction::new( "echo", @@ -545,11 +576,15 @@ fn signatures_trace_nominal_types_and_bound_receivers_use_the_actual_contract() vec![], )) .unwrap(); // Metadata alone traces the definition. + // 弱身份不保活定义,清理后升级 None 用于检查无隐藏根。 let weak = vm.downgrade(definition).unwrap(); vm.define_method(definition, "echo", function).unwrap(); + // 实例持有定义类型边,绑定方法再持有实例边。 let receiver = vm.new_object().unwrap(); vm.attach_type(receiver, definition).unwrap(); + // 归还原始定义根;其后存活来自实例/签名或执行检查根。 vm.release(definition).unwrap(); + // bind_attr 交付绑定对象拥有根,实际调用会先补接收者。 let Value::Object(bound) = vm.bind_attr(receiver, "echo").unwrap() else { panic!("bound method") }; @@ -563,24 +598,31 @@ fn signatures_trace_nominal_types_and_bound_receivers_use_the_actual_contract() vm.call(bound, &[Value::Number(9.into())]).unwrap(), Value::Number(9.into()) ); + // 归还测试每份拥有根,签名/捕获/方法内部边由 Heap 处理。 for object in [bound, receiver, function, roots.object, roots.type_type] { vm.release(object).unwrap(); } + // 清理可能由类型/签名/方法持有关系形成的无根环,断言堆归零。 vm.collect_cycles(); assert!(vm.upgrade(weak).unwrap().is_none()); assert_eq!(vm.object_count(), 0); } #[test] +/// 只读域中 Mutating/External 在回调前拒绝,普通调用仍允许进入。 fn declared_host_effects_are_rejected_before_read_only_dispatch() { use endor_core::{AnnotationType as T, CallEffect, CallSignature}; use std::sync::{ Arc, atomic::{AtomicUsize, Ordering}, }; + // 两个不可在只读域调用的声明分别测试,不能只覆盖其中一个。 for effect in [CallEffect::Mutating, CallEffect::External] { + // 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。 let mut vm = Vm::new().unwrap(); + // VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。 let calls = Arc::new(AtomicUsize::new(0)); + // 分配上下文载荷,签名类型与显式 captures 各自通过 trace 保活。 let function = vm .allocate(ContextNativeFunction::new( "effect", @@ -605,12 +647,16 @@ fn declared_host_effects_are_rejected_before_read_only_dispatch() { } #[test] +/// 旧式函数也执行完整签名/可变参数/返回检查,独立 arity 与签名不一致必须拒绝。 fn legacy_native_signatures_check_optional_variadic_types_and_metadata() { use endor_core::{AnnotationType as T, CallArity, CallEffect, CallSignature, NativeFunction}; + /// 旧式回调返回最后一个借用参数,空参数返回 Nil;拥有根仍由 VM 补充。 fn echo(args: &[Value]) -> Result { Ok(args.last().cloned().unwrap_or(Value::Nil)) } + // 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。 let mut vm = Vm::new().unwrap(); + // 分配上下文载荷,签名类型与显式 captures 各自通过 trace 保活。 let function = vm .allocate( NativeFunction::new("legacy", echo).with_signature(CallSignature { @@ -634,6 +680,7 @@ fn legacy_native_signatures_check_optional_variadic_types_and_metadata() { assert!( matches!(vm.call(function, &[]), Err(VmError::TypeMismatch { position, .. }) if position == "return") ); + // 故意同时配置互相矛盾的签名和独立 arity,查询/调用都应报元数据错误。 let invalid = vm .allocate( NativeFunction::new("invalid", echo) @@ -654,14 +701,19 @@ fn legacy_native_signatures_check_optional_variadic_types_and_metadata() { #[derive(Debug)] struct EraseThenDelegate { + /// 构造时快照的完整签名,包装载荷负责 trace 类型定义。 signature: endor_core::CallSignature, + /// 被委托源码函数,由包装载荷 captures 保活至回调中输出保护接管。 target: ObjectRef, } impl NativeCallable for EraseThenDelegate { + /// ContextNativeFunction 快照此完整元数据并 trace 定义边;clone 本身不增加外部根。 fn signature(&self) -> Option { Some(self.signature.clone()) } + /// 测试原生分派入口,回调内借用参数/目标,NativeContext 校验并移交 Return 或 Invoke 输出根。 fn invoke(&self, cx: &mut NativeContext<'_>, _: &[Value]) -> Result { + // 将当前函数换为普通 Record,移除签名及捕获边,迫使返回约束靠执行器自己的根存活。 cx.replace_payload(cx.callee(), endor_core::Record)?; Ok(NativeResult::Invoke { function: self.target, @@ -671,17 +723,23 @@ impl NativeCallable for EraseThenDelegate { } #[test] +/// 回调替换自己的载荷删除签名边后,委托链独立持有返回类型根,诊断完成后才释放。 fn delegated_return_type_survives_removal_of_the_original_signature_edges() { use endor_core::{ AnnotationType as T, CallEffect, CallSignature, CodeLayout, EndorCode, Instruction, Opcode, TypeConstructor, }; + // 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。 let mut vm = Vm::new().unwrap(); + // 显式自举 Object/Type,名义定义在同一对象图中受计数管理。 let roots = vm.bootstrap_types().unwrap(); + // 名义定义用于签名约束,归还测试根后仍需由签名边保活。 let definition = vm .new_type("Transient", roots.object, TypeConstructor::Record) .unwrap(); + // 弱身份不保活定义,清理后升级 None 用于检查无隐藏根。 let weak = vm.downgrade(definition).unwrap(); + // 最小源码函数返回固定常量,区分原生包装约束与源码帧自身的执行。 let code = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadConst, 0), @@ -691,13 +749,19 @@ fn delegated_return_type_survives_removal_of_the_original_signature_edges() { CodeLayout::default(), ) .unwrap(); + // 托管代码拥有常量强边,源码函数还会持有代码边。 let code = vm.new_code(code).unwrap(); + // 源码环境有分配根,函数持有它的内部边。 let globals = vm.new_dict().unwrap(); + // 源码函数返回文本或 Nil,作为后续返回契约一致/不一致的终点。 let source = vm .new_function("source", code, globals, vec![], vec![]) .unwrap(); + // 先构造完整数量/效果骨架,再替换联合类型项,最终查询/调用才验证完整性。 let mut signature = CallSignature::exact(vec![], T::Any, CallEffect::Mutating); + // 联合第一项引用将失去原载荷保活的 Transient,错误诊断仍应能读取其名字。 signature.returns = endor_core::CallType::Union(vec![T::Nominal(definition), T::Number]); + // 包装函数显式 captures 持有委托目标,输出目标根由 NativeContext 另取得。 let wrapper = vm .allocate(ContextNativeFunction::new( "erased", @@ -708,11 +772,13 @@ fn delegated_return_type_survives_removal_of_the_original_signature_edges() { vec![source], )) .unwrap(); + // 归还原始定义根;其后存活来自实例/签名或执行检查根。 vm.release(definition).unwrap(); assert!( matches!(vm.call(wrapper, &[]), Err(VmError::TypeMismatch { function, expected, .. }) if function == "erased" && expected == "Transient | Number") ); assert!(vm.upgrade(weak).unwrap().is_none()); + // 归还测试每份拥有根,签名/捕获/方法内部边由 Heap 处理。 for object in [ wrapper, source, @@ -723,22 +789,28 @@ fn delegated_return_type_survives_removal_of_the_original_signature_edges() { ] { vm.release(object).unwrap(); } + // 清理可能由类型/签名/方法持有关系形成的无根环,断言堆归零。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] +/// 联合各分支可接纳,非法后续类型及空联合不得被 Any/提前匹配掩盖。 fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_metadata() { use endor_core::{AnnotationType as T, CallEffect, CallSignature, CallType}; use std::sync::{ Arc, atomic::{AtomicUsize, Ordering}, }; + // 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。 let mut vm = Vm::new().unwrap(); + // VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。 let calls = Arc::new(AtomicUsize::new(0)); + // 先构造完整数量/效果骨架,再替换联合类型项,最终查询/调用才验证完整性。 let mut signature = CallSignature::exact(vec![T::Any], T::Any, CallEffect::ReadOnly); signature.parameters[0] = CallType::Union(vec![T::String, T::Bool]); signature.returns = signature.parameters[0].clone(); + // 分配上下文载荷,签名类型与显式 captures 各自通过 trace 保活。 let function = vm .allocate(ContextNativeFunction::new( "union", @@ -751,6 +823,7 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met vec![], )) .unwrap(); + // 每个合法联合/nullable 分支都实际调用一次,计数器验证非法输入未进入。 for value in [ Value::Text("capture".into()), Value::Bool(true), @@ -765,6 +838,7 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met matches!(vm.call(function, &[Value::Number(3.into())]), Err(VmError::TypeMismatch { expected, .. }) if expected == "String | Bool") ); assert_eq!(calls.load(Ordering::SeqCst), 3); + // 先构造完整数量/效果骨架,再替换联合类型项,最终查询/调用才验证完整性。 let mut signature = CallSignature::exact(vec![T::Any], T::Any, CallEffect::ReadOnly); signature.parameters[0] = CallType::nullable(T::Number); signature.returns = signature.parameters[0].clone(); @@ -782,15 +856,18 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met ), ) .unwrap(); + // 每个合法联合/nullable 分支都实际调用一次,计数器验证非法输入未进入。 for value in [Value::Nil, Value::Number(2.into())] { assert_eq!( vm.call(function, std::slice::from_ref(&value)).unwrap(), value ); } + // 普通 Record 不能用作 Nominal 的类型定义,即使前分支 Any 已能匹配也须拒绝。 let record = vm.new_object().unwrap(); + // 先构造完整数量/效果骨架,再替换联合类型项,最终查询/调用才验证完整性。 let mut signature = CallSignature::exact(vec![T::Any], T::Any, CallEffect::ReadOnly); - // A matching first branch must not hide a later invalid nominal definition. + // 完整联合先验证全部定义,不能因第一项 Any 匹配而跳过后面的非法类型句柄。 signature.parameters[0] = CallType::Union(vec![T::Any, T::Nominal(record)]); vm.replace_payload( function, @@ -808,6 +885,7 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met .unwrap(); assert!(vm.call(function, &[Value::Nil]).is_err()); assert_eq!(calls.load(Ordering::SeqCst), 5); + // 先构造完整数量/效果骨架,再替换联合类型项,最终查询/调用才验证完整性。 let mut signature = CallSignature::exact(vec![], T::Any, CallEffect::ReadOnly); signature.returns = CallType::Union(vec![]); vm.replace_payload( @@ -832,15 +910,20 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met } #[test] +/// Delegated 包装器能进入只读域,但实际目标仍在同域核对效果,目标换为纯函数后可通过。 fn delegated_effects_keep_the_original_read_only_scope_and_check_the_actual_target() { use endor_core::{AnnotationType as T, CallEffect, CallSignature}; use std::sync::{ Arc, atomic::{AtomicUsize, Ordering}, }; + // 独立堆隔离当前签名/效果场景,计数与拥有根不会跨测试污染。 let mut vm = Vm::new().unwrap(); + // VM 外原子计数记录回调实际进入次数,不以错误类型猜测是否产生副作用。 let calls = Arc::new(AtomicUsize::new(0)); + // 目标独立计数,用于证明包装器已经执行但目标在副作用前被效果检查拦截。 let target_calls = Arc::new(AtomicUsize::new(0)); + // 初始目标声明 Mutating,在只读委托链中不允许执行;随后替换成 ReadOnly。 let target = vm .allocate(ContextNativeFunction::new( "target", @@ -853,6 +936,7 @@ fn delegated_effects_keep_the_original_read_only_scope_and_check_the_actual_targ vec![], )) .unwrap(); + // 包装函数显式 captures 持有委托目标,输出目标根由 NativeContext 另取得。 let wrapper = vm .allocate(ContextNativeFunction::new( "wrapper", @@ -892,6 +976,7 @@ fn delegated_effects_keep_the_original_read_only_scope_and_check_the_actual_targ .0, Value::Nil ); + // 归还测试每份拥有根,签名/捕获/方法内部边由 Heap 处理。 for object in [wrapper, target] { vm.release(object).unwrap(); } -- 2.52.0 From 5ccdf3ee2cdf9496a5ab966fbd30d9964c62e292 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 18:07:05 +0800 Subject: [PATCH 31/62] docs(core): explain method substitution and update regression scenarios --- crates/core/tests/method_contracts.rs | 70 +++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/crates/core/tests/method_contracts.rs b/crates/core/tests/method_contracts.rs index 8269ca4..e8b7e1b 100644 --- a/crates/core/tests/method_contracts.rs +++ b/crates/core/tests/method_contracts.rs @@ -1,3 +1,4 @@ +//! 方法替换规则的集成回归:使用源码/原生完整签名,观察定义提交与调用前后契约边界。 //! Compile here; execute only in the designated remote validation environment. use endor_core::{ AnnotationType as T, CallEffect, CallSignature, CallType, CodeLayout, ContextNativeFunction, @@ -6,6 +7,7 @@ use endor_core::{ }; use std::collections::BTreeMap; +/// 测试辅助,生成先写 globals.ran=42 再返回 result 的源码方法,附显式 owner/参数/返回注解。 fn source( vm: &mut Vm, globals: ObjectRef, @@ -14,6 +16,7 @@ fn source( returns: &str, result: Value, ) -> ObjectRef { + // 手工指令保证源码体可观测,参数/局部槽按注解数量保持一致。 let code = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadConst, 0), @@ -36,13 +39,17 @@ fn source( method_owner: Some(owner.into()), }) .unwrap(); + // 代码先分配拥有根,后续函数持有它的内部边。 let code = vm.new_code(code).unwrap(); + // 辅助方法函数持有代码/全局,返回拥有根供类方法表引用。 let function = vm .new_function("check", code, globals, vec![], vec![]) .unwrap(); + // 归还辅助构造代码临时根,不释放函数内部拥有边。 vm.release(code).unwrap(); function } +/// 测试辅助,以给定基类构造只声明 check 方法、无自有字段的源码类。 fn class( vm: &mut Vm, name: &str, @@ -58,11 +65,17 @@ fn class( } #[test] +/// 创建派生类即检查参数可接受集合不能缩窄、返回集合不能扩大。 fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation() { + // 独立 VM 及名义类型图,避免全局重绑定与定义修改跨测试污染。 let mut vm = Vm::new().unwrap(); + // 显式自举 Object/Type 根,最终归还并收集其类型图环。 let roots = vm.bootstrap_types().unwrap(); + // 普通 override 检查显式开启,不能仅依赖能力 opt-in 强制规则。 vm.set_source_type_checks(true).unwrap(); + // 当前源码方法自己的注解解析环境,同时保存 ran 副作用标志。 let globals = vm.new_dict().unwrap(); + // 父签名确定可接受参数集合和返回上界,方法自身类名由 owner 解析。 let base_fn = source( &mut vm, globals, @@ -71,7 +84,9 @@ fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation() "Object", Value::Nil, ); + // 创建基类并提交父方法,自身没有父同名覆盖约束。 let base = class(&mut vm, "Base", roots.object, base_fn).unwrap(); + // 参数 Number 放宽到 Object,返回 Object 收窄到 Number,符合可替换关系。 let good_fn = source( &mut vm, globals, @@ -80,7 +95,9 @@ fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation() "Number", Value::Number(7.into()), ); + // 合法派生创建成功,保留其函数及基类引用。 let good = class(&mut vm, "Good", base, good_fn).unwrap(); + // String 不能接纳父方法允许的 Number,所以即使返回约束合法也应拒绝。 let narrow = source( &mut vm, globals, @@ -96,6 +113,7 @@ fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation() .. }) )); + // 另建父方法要求返回 Number,用于单独测试返回协变方向。 let numeric = source( &mut vm, globals, @@ -104,7 +122,9 @@ fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation() "Number", Value::Number(0.into()), ); + // 以返回 Number 的方法作为新基线,不和先前 Object 返回父方法混淆。 let numeric_type = class(&mut vm, "Numeric", roots.object, numeric).unwrap(); + // 返回 Object 比 Number 更宽,派生实现不能让父调用者失去数值返回保证。 let broad = source( &mut vm, globals, @@ -120,6 +140,7 @@ fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation() .. }) )); + // 归还每份测试拥有根,类方法/基类/签名内部边由 Heap 维护。 for object in [ good, good_fn, @@ -135,16 +156,23 @@ fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation() ] { vm.release(object).unwrap(); } + // 显式回收无根类型/函数图环,最后验证存活对象数为零。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] +/// 父方法拟议更新必须检查现有覆盖后代,失败保持父版本及双方原方法表。 fn replacing_an_ancestor_validates_overriding_descendants_before_mutation() { + // 独立 VM 及名义类型图,避免全局重绑定与定义修改跨测试污染。 let mut vm = Vm::new().unwrap(); + // 显式自举 Object/Type 根,最终归还并收集其类型图环。 let roots = vm.bootstrap_types().unwrap(); + // 普通 override 检查显式开启,不能仅依赖能力 opt-in 强制规则。 vm.set_source_type_checks(true).unwrap(); + // 当前源码方法自己的注解解析环境,同时保存 ran 副作用标志。 let globals = vm.new_dict().unwrap(); + // 父签名确定可接受参数集合和返回上界,方法自身类名由 owner 解析。 let base_fn = source( &mut vm, globals, @@ -153,7 +181,9 @@ fn replacing_an_ancestor_validates_overriding_descendants_before_mutation() { "Object", Value::Nil, ); + // 创建基类并提交父方法,自身没有父同名覆盖约束。 let base = class(&mut vm, "Base", roots.object, base_fn).unwrap(); + // 后代自己的签名需与当前父签名保持替换关系,即使后代已先创建。 let child_fn = source( &mut vm, globals, @@ -162,7 +192,9 @@ fn replacing_an_ancestor_validates_overriding_descendants_before_mutation() { "Number", Value::Number(0.into()), ); + // 先创建当前合法后代,再对父方法提出可能使它失效的更新。 let child = class(&mut vm, "Child", base, child_fn).unwrap(); + // 父参数从 Number 放宽到 Object 后,现有子方法仍只接收 Number,将不再可替换。 let replacement = source( &mut vm, globals, @@ -171,6 +203,7 @@ fn replacing_an_ancestor_validates_overriding_descendants_before_mutation() { "Object", Value::Nil, ); + // 记录父定义版本,验证失败在真实载荷替换前发生。 let version = vm.change_version(base).unwrap(); assert!(matches!( vm.define_method(base, "check", replacement), @@ -185,6 +218,7 @@ fn replacing_an_ancestor_validates_overriding_descendants_before_mutation() { vm.object_store().lookup_method(child, "check").unwrap(), Some(child_fn) ); + // 归还每份测试拥有根,类方法/基类/签名内部边由 Heap 维护。 for object in [ replacement, child, @@ -197,27 +231,38 @@ fn replacing_an_ancestor_validates_overriding_descendants_before_mutation() { ] { vm.release(object).unwrap(); } + // 显式回收无根类型/函数图环,最后验证存活对象数为零。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] +/// 类型名全局重绑定可破坏已有覆盖关系,调用前须重验并阻止体副作用,恢复绑定后可再调用。 fn global_rebinding_is_rechecked_before_the_source_body_and_recovers() { + // 独立 VM 及名义类型图,避免全局重绑定与定义修改跨测试污染。 let mut vm = Vm::new().unwrap(); + // 显式自举 Object/Type 根,最终归还并收集其类型图环。 let roots = vm.bootstrap_types().unwrap(); + // 普通 override 检查显式开启,不能仅依赖能力 opt-in 强制规则。 vm.set_source_type_checks(true).unwrap(); + // Result 基类型是父方法返回约束,ResultChild 初始满足其协变要求。 let parent = vm .new_type("Result", roots.object, TypeConstructor::Record) .unwrap(); + // 派生返回类型起初是父返回类型子类,后续全局绑定将故意破坏该关系。 let result_type = vm .new_type("ResultChild", parent, TypeConstructor::Record) .unwrap(); + // 无关类型可实例化但不是 Result 子类,不能作为合法协变返回类型。 let other = vm .new_type("Other", roots.object, TypeConstructor::Record) .unwrap(); + // 实际返回对象先保留测试根,源码常量表也会持有它的边。 let result = vm.new_object().unwrap(); vm.attach_type(result, result_type).unwrap(); + // 父方法独立环境固定 Result,子环境 Actual 重绑定不应改变父约束。 let base_globals = vm.new_dict().unwrap(); + // 当前源码方法自己的注解解析环境,同时保存 ran 副作用标志。 let globals = vm.new_dict().unwrap(); vm.dict_set( base_globals, @@ -233,6 +278,7 @@ fn global_rebinding_is_rechecked_before_the_source_body_and_recovers() { .unwrap(); vm.dict_set(globals, Value::Text("ran".into()), Value::Number(0.into())) .unwrap(); + // 父签名确定可接受参数集合和返回上界,方法自身类名由 owner 解析。 let base_fn = source( &mut vm, base_globals, @@ -241,7 +287,9 @@ fn global_rebinding_is_rechecked_before_the_source_body_and_recovers() { "Result", Value::Nil, ); + // 创建基类并提交父方法,自身没有父同名覆盖约束。 let base = class(&mut vm, "Base", roots.object, base_fn).unwrap(); + // 后代自己的签名需与当前父签名保持替换关系,即使后代已先创建。 let child_fn = source( &mut vm, globals, @@ -250,7 +298,9 @@ fn global_rebinding_is_rechecked_before_the_source_body_and_recovers() { "Actual", Value::Object(result), ); + // 先创建当前合法后代,再对父方法提出可能使它失效的更新。 let child = class(&mut vm, "Child", base, child_fn).unwrap(); + // 显式 self 为 Child 实例,调用时据其继承链解析各方法 own-class 注解。 let receiver = vm.new_object().unwrap(); vm.attach_type(receiver, child).unwrap(); vm.dict_set(globals, Value::Text("Actual".into()), Value::Object(other)) @@ -269,9 +319,12 @@ fn global_rebinding_is_rechecked_before_the_source_body_and_recovers() { Value::Object(result_type), ) .unwrap(); + // 恢复合法全局绑定后调用成功,结果根仍须与原测试对象根分别归还。 let value = vm.call(child_fn, &[Value::Object(receiver)]).unwrap(); assert_eq!(value, Value::Object(result)); + // 只释放本次调用新增的结果根,源码常量及测试原根还在。 vm.release_value(value).unwrap(); + // 归还每份测试拥有根,类方法/基类/签名内部边由 Heap 维护。 for object in [ receiver, child, @@ -289,34 +342,46 @@ fn global_rebinding_is_rechecked_before_the_source_body_and_recovers() { ] { vm.release(object).unwrap(); } + // 显式回收无根类型/函数图环,最后验证存活对象数为零。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[derive(Debug)] +/// 无捕获测试回调,签名通过包装载荷 trace 保活,不手动按定义身份 retain。 struct Native(CallSignature); impl NativeCallable for Native { + /// 包装载荷取得完整签名快照,定义引用由 ContextNativeFunction trace 持有。 fn signature(&self) -> Option { Some(self.0.clone()) } + /// 测试原生体只返回直接 Number;被拒绝场景主要检查声明的替换关系而非运行副作用。 fn invoke(&self, _: &mut NativeContext<'_>, _: &[Value]) -> Result { Ok(NativeResult::Return(Value::Number(0.into()))) } } #[test] +/// 原生覆盖不能丢弃父联合参数分支,也不能把已知 ReadOnly 扩大为 Mutating。 fn native_overrides_preserve_union_inputs_and_do_not_expand_effects() { + // 独立 VM 及名义类型图,避免全局重绑定与定义修改跨测试污染。 let mut vm = Vm::new().unwrap(); + // 显式自举 Object/Type 根,最终归还并收集其类型图环。 let roots = vm.bootstrap_types().unwrap(); + // 普通 override 检查显式开启,不能仅依赖能力 opt-in 强制规则。 vm.set_source_type_checks(true).unwrap(); + // 原生场景的名义 Base,签名首项按它校验接收者。 let base = vm .new_type("Base", roots.object, TypeConstructor::Record) .unwrap(); + // 先建立完整父签名,再把普通参数换为联合 Number | String。 let mut signature = CallSignature::exact( vec![T::Nominal(base), T::Any], T::Number, CallEffect::ReadOnly, ); + // 保留两条可接受输入分支,子实现必须全部接纳。 signature.parameters[1] = CallType::Union(vec![T::Number, T::String]); + // ContextNativeFunction 签名快照持有 Base 定义,方法表又持函数边,最终可能成环。 let base_fn = vm .allocate(ContextNativeFunction::new( "check", @@ -325,7 +390,9 @@ fn native_overrides_preserve_union_inputs_and_do_not_expand_effects() { )) .unwrap(); vm.define_method(base, "check", base_fn).unwrap(); + // 接收者由 Base 派生,子签名仍须接受声明类型实例。 let child = vm.new_type("Child", base, TypeConstructor::Record).unwrap(); + // 输入 Any 足够宽,但 Mutating 扩大父 ReadOnly 效果,必须拒绝。 let effect_fn = vm .allocate(ContextNativeFunction::new( "effect", @@ -344,6 +411,7 @@ fn native_overrides_preserve_union_inputs_and_do_not_expand_effects() { .. }) )); + // 效果保持只读却只接受 Number,丢失父 String 分支,同样拒绝。 let narrow_fn = vm .allocate(ContextNativeFunction::new( "narrow", @@ -362,6 +430,7 @@ fn native_overrides_preserve_union_inputs_and_do_not_expand_effects() { .. }) )); + // 归还每份测试拥有根,类方法/基类/签名内部边由 Heap 维护。 for object in [ narrow_fn, effect_fn, @@ -373,6 +442,7 @@ fn native_overrides_preserve_union_inputs_and_do_not_expand_effects() { ] { vm.release(object).unwrap(); } + // 显式回收无根类型/函数图环,最后验证存活对象数为零。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } -- 2.52.0 From 0c69dd83ba64e4437bdb1b8cdd064fcf0a370329 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 18:08:27 +0800 Subject: [PATCH 32/62] docs(core): explain dynamic contract and effect graph regressions --- crates/core/tests/method_contracts.rs | 84 ++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/crates/core/tests/method_contracts.rs b/crates/core/tests/method_contracts.rs index e8b7e1b..40bda5d 100644 --- a/crates/core/tests/method_contracts.rs +++ b/crates/core/tests/method_contracts.rs @@ -448,13 +448,19 @@ fn native_overrides_preserve_union_inputs_and_do_not_expand_effects() { } #[test] +/// 构类时可暂缓未知前向注解,调用时必须解析并重验覆盖;未知和已知但不兼容都不能进入源码体。 fn unknown_forward_names_defer_until_call_without_entering_the_body() { + // 独立 VM 隔离全局与方法重绑定,避免其他场景状态干扰快照证明。 let mut vm = Vm::new().unwrap(); + // 显式类型自举根,用于实例与绑定方法测试,最终归还并收集类型环。 let roots = vm.bootstrap_types().unwrap(); + // 启用普通覆盖验证,前向未知仅在定义阶段暂缓,调用阶段不豁免。 vm.set_source_type_checks(true).unwrap(); + // 源码名查找环境,更新其中键可改变契约或调用图,不能把首次查询缓存为永久证明。 let globals = vm.new_dict().unwrap(); vm.dict_set(globals, Value::Text("ran".into()), Value::Number(0.into())) .unwrap(); + // 父方法要求 Number 参数,子类尚未定义的 Later 参数暂缓到调用检查。 let base_fn = source( &mut vm, globals, @@ -463,7 +469,9 @@ fn unknown_forward_names_defer_until_call_without_entering_the_body() { "Number", Value::Number(0.into()), ); + // 父定义先合法创建,后代契约需相对这份真实定义检查。 let base = class(&mut vm, "Base", roots.object, base_fn).unwrap(); + // 子类使用尚未解析的 Later 名称,不代表可永久跳过参数替换验证。 let child_fn = source( &mut vm, globals, @@ -472,7 +480,9 @@ fn unknown_forward_names_defer_until_call_without_entering_the_body() { "Number", Value::Number(0.into()), ); + // 定义阶段允许未知名称的派生创建,实例调用时会重新核对。 let child = class(&mut vm, "Child", base, child_fn).unwrap(); + // 实例提供实际接收者身份,绑定效果分析可据此查 helper,未绑定函数则未知。 let receiver = vm.new_object().unwrap(); vm.attach_type(receiver, child).unwrap(); assert!(matches!( @@ -482,6 +492,7 @@ fn unknown_forward_names_defer_until_call_without_entering_the_body() { ), Err(VmError::UnknownAnnotation(_)) )); + // Later 最终解析为独立名义类型,不接纳父参数 Number,故由未知错误变为覆盖不兼容。 let later = vm .new_type("Later", roots.object, TypeConstructor::Record) .unwrap(); @@ -498,6 +509,7 @@ fn unknown_forward_names_defer_until_call_without_entering_the_body() { vm.dict_get(globals, &Value::Text("ran".into())).unwrap(), Some(Value::Number(0.into())) ); + // 归还测试拥有根,内部方法/签名/类型边由堆维护,避免重复 release。 for object in [ receiver, child, @@ -511,16 +523,23 @@ fn unknown_forward_names_defer_until_call_without_entering_the_body() { ] { vm.release(object).unwrap(); } + // 回收无根函数/全局/类型环,最终对象数应为零。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] +/// 已验证源码只读效果约束原生覆盖;Mutating/External 拒绝,Delegated 仍为未知而不是确定只读。 fn verified_source_read_only_effects_constrain_native_overrides() { + // 独立 VM 隔离全局与方法重绑定,避免其他场景状态干扰快照证明。 let mut vm = Vm::new().unwrap(); + // 显式类型自举根,用于实例与绑定方法测试,最终归还并收集类型环。 let roots = vm.bootstrap_types().unwrap(); + // 启用普通覆盖验证,前向未知仅在定义阶段暂缓,调用阶段不豁免。 vm.set_source_type_checks(true).unwrap(); + // 源码名查找环境,更新其中键可改变契约或调用图,不能把首次查询缓存为永久证明。 let globals = vm.new_dict().unwrap(); + // 手工指令使控制流和调用目标来源可精确观察,构造器先验证栈高/跳转。 let code = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadFast, 1), @@ -541,26 +560,35 @@ fn verified_source_read_only_effects_constrain_native_overrides() { }) .unwrap(); assert!(code.is_statically_read_only()); + // 代码数据只验证与分配,call_effect 不应实际执行其中指令。 let code = vm.new_code(code).unwrap(); + // 源码方法只装载参数并返回,可由本地白名单直接证明 ReadOnly。 let source = vm .new_function("check", code, globals, vec![], vec![]) .unwrap(); + // 父定义先合法创建,后代契约需相对这份真实定义检查。 let base = class(&mut vm, "PureBase", roots.object, source).unwrap(); + // 后代继承已证明只读的方法,候选原生覆盖不能扩大已知效果。 let child = vm.new_type("Child", base, TypeConstructor::Record).unwrap(); assert_eq!(vm.call_effect(source).unwrap(), Some(CallEffect::ReadOnly)); + // 实例提供实际接收者身份,绑定效果分析可据此查 helper,未绑定函数则未知。 let receiver = vm.new_object().unwrap(); vm.attach_type(receiver, child).unwrap(); + // 绑定对象拥有函数和实际接收者边,分析可用 receiver 区分实例方法/字段。 let Value::Object(bound) = vm.bind_attr(receiver, "check").unwrap() else { panic!("bound method") }; assert_eq!(vm.call_effect(bound).unwrap(), Some(CallEffect::ReadOnly)); + // 保存所有候选原生函数拥有根,避免只清理最后成功安装的方法。 let mut functions = vec![]; + // 四种原生声明分别测试;未知 Delegated 不等于允许在只读域执行任意目标。 for effect in [ CallEffect::Mutating, CallEffect::External, CallEffect::ReadOnly, CallEffect::Delegated, ] { + // 函数持有其代码/全局边,效果查询从当前载荷及环境读取快照。 let function = vm .allocate(ContextNativeFunction::new( "check", @@ -572,8 +600,11 @@ fn verified_source_read_only_effects_constrain_native_overrides() { vec![], )) .unwrap(); + // 失败更新不能改变后代定义版本,用于证明检查在提交方法表之前完成。 let version = vm.change_version(child).unwrap(); + // 保存更新结果,再按已知效果是否扩大区分成功与拒绝。 let update = vm.define_method(child, "check", function); + // Mutating/External 扩大 ReadOnly;其余候选按当前规则检查与查询。 if matches!(effect, CallEffect::Mutating | CallEffect::External) { assert!(matches!( update, @@ -592,6 +623,7 @@ fn verified_source_read_only_effects_constrain_native_overrides() { ); functions.push(function); } + // 归还测试拥有根,内部方法/签名/类型边由堆维护,避免重复 release。 for object in functions.into_iter().chain([ bound, receiver, @@ -605,12 +637,15 @@ fn verified_source_read_only_effects_constrain_native_overrides() { ]) { vm.release(object).unwrap(); } + // 回收无根函数/全局/类型环,最终对象数应为零。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] +/// 局部共享引用赋值可能修改旧对象,Cell 写入/未知调用也不能由本地白名单直接证明纯。 fn shared_local_assignment_and_dynamic_calls_are_not_inferred_pure() { + // StoreFast 可能触发旧对象共享引用赋值,不能仅因目标是局部变量就判纯。 let assignment = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadFast, 1), @@ -627,6 +662,7 @@ fn shared_local_assignment_and_dynamic_calls_are_not_inferred_pure() { ) .unwrap(); assert!(!assignment.is_statically_read_only()); + // StoreDeref 修改已有捕获 Cell,必须从本地只读白名单排除。 let captured = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadConst, 0), @@ -642,6 +678,7 @@ fn shared_local_assignment_and_dynamic_calls_are_not_inferred_pure() { ) .unwrap(); assert!(!captured.is_statically_read_only()); + // 动态 Call 没有本地目标证明,is_statically_read_only 不能直接通过。 let call = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadGlobal, 0), @@ -659,7 +696,9 @@ fn shared_local_assignment_and_dynamic_calls_are_not_inferred_pure() { assert!(!call.is_statically_read_only()); } +/// 效果测试辅助,手工建立调用指定全局名并返回的源码函数,不在查询时执行。 fn global_caller(vm: &mut Vm, globals: ObjectRef, name: &str) -> ObjectRef { + // 手工指令使控制流和调用目标来源可精确观察,构造器先验证栈高/跳转。 let code = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadGlobal, 0), @@ -674,18 +713,25 @@ fn global_caller(vm: &mut Vm, globals: ObjectRef, name: &str) -> ObjectRef { }, ) .unwrap(); + // 代码数据只验证与分配,call_effect 不应实际执行其中指令。 let code = vm.new_code(code).unwrap(); + // 函数持有其代码/全局边,效果查询从当前载荷及环境读取快照。 let function = vm .new_function("caller", code, globals, vec![], vec![]) .unwrap(); + // 辅助函数归还临时代码根,函数仍保留代码对象。 vm.release(code).unwrap(); function } #[test] +/// 调用效果快照随全局遮蔽、内置回退、闭包 Cell 及递归边变化重新求证,不保留过期目标。 fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() { + // 独立 VM 隔离全局与方法重绑定,避免其他场景状态干扰快照证明。 let mut vm = Vm::new().unwrap(); + // 源码名查找环境,更新其中键可改变契约或调用图,不能把首次查询缓存为永久证明。 let globals = vm.new_dict().unwrap(); + // 函数创建时捕获该内置字典身份,全局缺名才回退这里。 let builtins = vm.new_dict().unwrap(); vm.dict_set( globals, @@ -693,6 +739,7 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() { Value::Object(builtins), ) .unwrap(); + // 明确声明 ReadOnly 的原生目标,作为可证明调用边。 let pure = vm .allocate(ContextNativeFunction::new( "pure", @@ -704,6 +751,7 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() { vec![], )) .unwrap(); + // 声明 Mutating/External 的目标,使源码整体调用效果无法证明 ReadOnly。 let impure = vm .allocate(ContextNativeFunction::new( "impure", @@ -717,7 +765,9 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() { .unwrap(); vm.dict_set(builtins, Value::Text("target".into()), Value::Object(pure)) .unwrap(); + // 第一层从环境解析 target,初始由内置字典提供 pure。 let wrapper = global_caller(&mut vm, globals, "target"); + // 外层再经全局 wrapper 调用,检验传递分析不只看一跳。 let outer = global_caller(&mut vm, globals, "wrapper"); vm.dict_set( globals, @@ -732,7 +782,9 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() { vm.dict_delete(globals, &Value::Text("target".into())) .unwrap(); assert_eq!(vm.call_effect(outer).unwrap(), Some(CallEffect::ReadOnly)); + // 闭包捕获目标通过共享 Cell 更新,函数身份不变而调用图可变化。 let cell = vm.new_cell(Some(Value::Object(pure))).unwrap(); + // 手工指令使控制流和调用目标来源可精确观察,构造器先验证栈高/跳转。 let code = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadDeref, 0), @@ -747,7 +799,9 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() { }, ) .unwrap(); + // 代码数据只验证与分配,call_effect 不应实际执行其中指令。 let code = vm.new_code(code).unwrap(); + // 源码经 LoadDeref 取目标再 Call,分析需读 Cell 当前内容而不执行函数体。 let closure = vm .new_function("closure", code, globals, vec![], vec![cell]) .unwrap(); @@ -756,6 +810,7 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() { assert_eq!(vm.call_effect(closure).unwrap(), None); vm.cell_set(cell, Value::Object(pure)).unwrap(); assert_eq!(vm.call_effect(closure).unwrap(), Some(CallEffect::ReadOnly)); + // 全局同名绑定回自己形成不终止调用图;查询必须通过去重收敛而非执行。 let recursive = global_caller(&mut vm, globals, "recursive"); vm.dict_set( globals, @@ -763,7 +818,7 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() { Value::Object(recursive), ) .unwrap(); - // No function body is executed, including this non-terminating call graph. + // 查询不会执行任何源码体;即使图有无限自递归,visited 去重也应使分析结束。 assert_eq!( vm.call_effect(recursive).unwrap(), Some(CallEffect::ReadOnly) @@ -775,19 +830,25 @@ fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() { ) .unwrap(); assert_eq!(vm.call_effect(recursive).unwrap(), None); + // 归还测试拥有根,内部方法/签名/类型边由堆维护,避免重复 release。 for object in [ recursive, closure, code, cell, outer, wrapper, pure, impure, builtins, globals, ] { vm.release(object).unwrap(); } + // 回收无根函数/全局/类型环,最终对象数应为零。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] +/// 条件两路径必须汇合事实,目标不一致时不可保留旧的已知纯目标;恢复相同目标后可重新证明。 fn effect_control_flow_joins_do_not_keep_a_stale_known_call_target() { + // 独立 VM 隔离全局与方法重绑定,避免其他场景状态干扰快照证明。 let mut vm = Vm::new().unwrap(); + // 源码名查找环境,更新其中键可改变契约或调用图,不能把首次查询缓存为永久证明。 let globals = vm.new_dict().unwrap(); + // 明确声明 ReadOnly 的原生目标,作为可证明调用边。 let pure = vm .allocate(ContextNativeFunction::new( "pure", @@ -799,6 +860,7 @@ fn effect_control_flow_joins_do_not_keep_a_stale_known_call_target() { vec![], )) .unwrap(); + // 声明 Mutating/External 的目标,使源码整体调用效果无法证明 ReadOnly。 let impure = vm .allocate(ContextNativeFunction::new( "impure", @@ -810,10 +872,12 @@ fn effect_control_flow_joins_do_not_keep_a_stale_known_call_target() { vec![], )) .unwrap(); + // 两个分支初始指向同一 pure 身份,汇合可以保留该确定目标。 for name in ["left", "right"] { vm.dict_set(globals, Value::Text(name.into()), Value::Object(pure)) .unwrap(); } + // 手工指令使控制流和调用目标来源可精确观察,构造器先验证栈高/跳转。 let code = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadFast, 0), @@ -834,7 +898,9 @@ fn effect_control_flow_joins_do_not_keep_a_stale_known_call_target() { }, ) .unwrap(); + // 代码数据只验证与分配,call_effect 不应实际执行其中指令。 let code = vm.new_code(code).unwrap(); + // 函数持有其代码/全局边,效果查询从当前载荷及环境读取快照。 let function = vm .new_function("choose", code, globals, vec![], vec![]) .unwrap(); @@ -851,21 +917,29 @@ fn effect_control_flow_joins_do_not_keep_a_stale_known_call_target() { vm.call_effect(function).unwrap(), Some(CallEffect::ReadOnly) ); + // 归还测试拥有根,内部方法/签名/类型边由堆维护,避免重复 release。 for object in [function, code, pure, impure, globals] { vm.release(object).unwrap(); } + // 回收无根函数/全局/类型环,最终对象数应为零。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] +/// 绑定接收者提供方法目标身份;类方法更新和实例字段遮蔽都必须影响效果查询。 fn bound_source_effects_follow_the_actual_receiver_method_and_instance_field() { + // 独立 VM 隔离全局与方法重绑定,避免其他场景状态干扰快照证明。 let mut vm = Vm::new().unwrap(); + // 显式类型自举根,用于实例与绑定方法测试,最终归还并收集类型环。 let roots = vm.bootstrap_types().unwrap(); + // 源码名查找环境,更新其中键可改变契约或调用图,不能把首次查询缓存为永久证明。 let globals = vm.new_dict().unwrap(); + // Receiver 名义类型保存 helper/check 方法,实例可另以字段遮蔽 helper。 let definition = vm .new_type("Receiver", roots.object, TypeConstructor::Record) .unwrap(); + // 明确声明 ReadOnly 的原生目标,作为可证明调用边。 let pure = vm .allocate(ContextNativeFunction::new( "helper", @@ -877,6 +951,7 @@ fn bound_source_effects_follow_the_actual_receiver_method_and_instance_field() { vec![], )) .unwrap(); + // 声明 Mutating/External 的目标,使源码整体调用效果无法证明 ReadOnly。 let impure = vm .allocate(ContextNativeFunction::new( "helper", @@ -888,6 +963,7 @@ fn bound_source_effects_follow_the_actual_receiver_method_and_instance_field() { vec![], )) .unwrap(); + // 手工指令使控制流和调用目标来源可精确观察,构造器先验证栈高/跳转。 let code = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadFast, 0), @@ -905,14 +981,18 @@ fn bound_source_effects_follow_the_actual_receiver_method_and_instance_field() { }, ) .unwrap(); + // 代码数据只验证与分配,call_effect 不应实际执行其中指令。 let code = vm.new_code(code).unwrap(); + // 函数持有其代码/全局边,效果查询从当前载荷及环境读取快照。 let function = vm .new_function("check", code, globals, vec![], vec![]) .unwrap(); vm.define_method(definition, "helper", pure).unwrap(); vm.define_method(definition, "check", function).unwrap(); + // 实例提供实际接收者身份,绑定效果分析可据此查 helper,未绑定函数则未知。 let receiver = vm.new_object().unwrap(); vm.attach_type(receiver, definition).unwrap(); + // 绑定对象拥有函数和实际接收者边,分析可用 receiver 区分实例方法/字段。 let Value::Object(bound) = vm.bind_attr(receiver, "check").unwrap() else { panic!("bound") }; @@ -927,6 +1007,7 @@ fn bound_source_effects_follow_the_actual_receiver_method_and_instance_field() { assert_eq!(vm.call_effect(bound).unwrap(), None); vm.del_attr(receiver, "helper").unwrap(); assert_eq!(vm.call_effect(bound).unwrap(), Some(CallEffect::ReadOnly)); + // 归还测试拥有根,内部方法/签名/类型边由堆维护,避免重复 release。 for object in [ bound, receiver, @@ -941,6 +1022,7 @@ fn bound_source_effects_follow_the_actual_receiver_method_and_instance_field() { ] { vm.release(object).unwrap(); } + // 回收无根函数/全局/类型环,最终对象数应为零。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } -- 2.52.0 From 2ddb26c5413a698c350f37ec99545b1599fddbb5 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 18:09:50 +0800 Subject: [PATCH 33/62] docs(core): explain bytecode validation and basic execution regressions --- crates/core/tests/execution.rs | 69 +++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/crates/core/tests/execution.rs b/crates/core/tests/execution.rs index 7ba32dd..a093c2c 100644 --- a/crates/core/tests/execution.rs +++ b/crates/core/tests/execution.rs @@ -1,3 +1,6 @@ +//! 通过手工指令验证 VM 求值、根生命周期及暂停行为,测试框架直接调用,无需源码解析器。 +//! op/simple/make_code 统一构造已验证代码;错误编码用 EndorCode 直接检验构造阶段拒绝。 + use std::any::Any; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -8,15 +11,19 @@ use endor_core::{ Value, Vm, VmError, }; +/// 测试辅助,构造完整 u32 操作数逻辑指令,实际扩展前缀由编码器生成。 fn op(opcode: Opcode, argument: u32) -> Instruction { Instruction::new(opcode, argument) } +/// 测试辅助,构造参数为零的无操作数指令,不手写物理编码。 fn simple(opcode: Opcode) -> Instruction { Instruction::simple(opcode) } +/// 测试辅助,创建直接文本 Value,不分配堆字符串。 fn text(value: &str) -> Value { Value::Text(value.into()) } +/// 测试辅助,编码并验证指令/常量/布局后分配代码拥有根;失败直接使测试失败。 fn make_code( vm: &mut Vm, instructions: &[Instruction], @@ -26,6 +33,7 @@ fn make_code( vm.new_code(EndorCode::from_instructions(instructions, constants, layout).unwrap()) .unwrap() } +/// 其他执行测试复用的最小返回常量代码,根由调用测试归还。 fn return_constant(vm: &mut Vm, value: Value) -> ObjectRef { make_code( vm, @@ -36,41 +44,57 @@ fn return_constant(vm: &mut Vm, value: Value) -> ObjectRef { } #[derive(Debug)] +/// 可选原地赋值测试载荷;只保存 Rust 原子数,不含可变托管对象边。 struct SharedReference(AtomicUsize); impl ObjectPayload for SharedReference { + /// Store 下转型识别测试载荷;与语言名义类型分开。 fn as_any(&self) -> &dyn Any { self } + /// 测试错误/诊断类别。 fn kind_name(&self) -> &'static str { "shared-reference" } + /// Store 可选旧式原地赋值协议,仅支持同类且先校验后原子更新,不改变托管引用边。 fn assign_from(&self, source: &dyn ObjectPayload) -> Result { + // 异类载荷返回 false 表示不做原地赋值,不是错误,也不修改目标。 let Some(source) = source.as_any().downcast_ref::() else { return Ok(false); }; + // 先读候选内容再校验,拒绝值不能留下半写状态。 let value = source.0.load(Ordering::SeqCst); + // 注入明确协议错误,验证执行器清理而不是静默重绑定。 if value == 99 { return Err(VmError::InvalidArgument("rejected replacement")); } + // 只更新宿主原子状态,不引入未 trace 的托管引用边。 self.0.store(value, Ordering::SeqCst); Ok(true) } } #[test] +/// 局部与全局赋值都保留共享对象旧身份并更新内容,已有 alias 可见,清理不泄漏根。 fn opt_in_assignment_keeps_alias_identity_ordinary_values_and_root_accounting() { + // 同一字节码模板分别覆盖 StoreFast 与 StoreGlobal,避免只测试一条赋值入口。 for local in [false, true] { + // 独立 VM 隔离对象计数/全局绑定,清理断言只针对本场景。 let mut vm = Vm::new().unwrap(); + // 共享目标以 1 初始化,同时另由 alias 持有;赋值后身份应保持。 let old = vm.allocate(SharedReference(AtomicUsize::new(1))).unwrap(); + // 候选共享值初始化为 2,复制内容到 old 不等于让 alias 指向此新身份。 let new = vm.allocate(SharedReference(AtomicUsize::new(2))).unwrap(); + // 函数执行环境与名称表索引对应,字典内部边独立于测试外部根。 let globals = vm.new_dict().unwrap(); vm.dict_set(globals, text("alias"), Value::Object(old)) .unwrap(); + // 只切换变量访问策略,操作数零在两类布局中分别代表槽/全局名。 let (store, load) = if local { (Opcode::StoreFast, Opcode::LoadFast) } else { (Opcode::StoreGlobal, Opcode::LoadGlobal) }; + // 操作数和布局在构造时校验,execute 期间测试实际值流与引用行为。 let code = make_code( &mut vm, &[ @@ -82,6 +106,7 @@ fn opt_in_assignment_keeps_alias_identity_ordinary_values_and_root_accounting() simple(Opcode::ReturnValue), ], vec![Value::Object(old), Value::Object(new)], + // 局部模板使用 locals,另一分支使用 names;不能把两类索引混用。 if local { CodeLayout { locals: vec!["value".into()], @@ -94,6 +119,7 @@ fn opt_in_assignment_keeps_alias_identity_ordinary_values_and_root_accounting() } }, ); + // 执行结果拥有根,读取完成后必须单独归还,不能当作纯借用复制。 let value = vm.execute(code, globals).unwrap(); assert_eq!(value, Value::Object(old)); assert_eq!( @@ -107,8 +133,11 @@ fn opt_in_assignment_keeps_alias_identity_ordinary_values_and_root_accounting() .load(Ordering::SeqCst), 2 ); + // 归还结果根后,代码常量、全局 alias 和测试根仍按各自拥有关系存在。 vm.release_value(value).unwrap(); + // 归还代码根同时移除常量表内部边,不重复释放常量对象的测试根。 vm.release(code).unwrap(); + // 移除全局内部边,使后续测试根归还后图真正为空。 vm.dict_clear(globals).unwrap(); vm.release(globals).unwrap(); vm.release(old).unwrap(); @@ -118,13 +147,19 @@ fn opt_in_assignment_keeps_alias_identity_ordinary_values_and_root_accounting() } #[test] +/// 赋值协议拒绝新值时旧对象内容/全局绑定不变,执行错误根由统一帧清理归还。 fn failed_opt_in_assignment_preserves_target_and_cleans_execution_roots() { + // 独立 VM 隔离对象计数/全局绑定,清理断言只针对本场景。 let mut vm = Vm::new().unwrap(); + // 共享目标以 1 初始化,同时另由 alias 持有;赋值后身份应保持。 let old = vm.allocate(SharedReference(AtomicUsize::new(1))).unwrap(); + // 以哨兵值 99 触发校验拒绝,不允许污染旧目标值。 let rejected = vm.allocate(SharedReference(AtomicUsize::new(99))).unwrap(); + // 函数执行环境与名称表索引对应,字典内部边独立于测试外部根。 let globals = vm.new_dict().unwrap(); vm.dict_set(globals, text("value"), Value::Object(old)) .unwrap(); + // 操作数和布局在构造时校验,execute 期间测试实际值流与引用行为。 let code = make_code( &mut vm, &[ @@ -151,7 +186,9 @@ fn failed_opt_in_assignment_preserves_target_and_cleans_execution_roots() { vm.dict_get(globals, &text("value")).unwrap(), Some(Value::Object(old)) ); + // 归还代码根同时移除常量表内部边,不重复释放常量对象的测试根。 vm.release(code).unwrap(); + // 移除全局内部边,使后续测试根归还后图真正为空。 vm.dict_clear(globals).unwrap(); vm.release(globals).unwrap(); vm.release(old).unwrap(); @@ -160,28 +197,38 @@ fn failed_opt_in_assignment_preserves_target_and_cleans_execution_roots() { } #[test] +/// 检查四字节参数前缀编码,再用第 256 号常量验证扩展操作数执行与偏移。 fn wordcode_extended_operands_decode_and_execute_without_truncation() { + // 只编码操作数不做常量索引验证,用固定字节向量证明高位未被截断。 let encoded = Instruction::encode_all(&[op(Opcode::LoadConst, 0x01020304)]).unwrap(); assert_eq!(encoded, vec![255, 1, 255, 2, 255, 3, 1, 4]); + // 独立 VM 隔离对象计数/全局绑定,清理断言只针对本场景。 let mut vm = Vm::new().unwrap(); + // 常量表至少 257 项才能合法引用索引 256。 let mut constants = vec![Value::Nil; 257]; + // 将边界索引放置独特值,执行返回可检出仅保留低八位的错误。 constants[256] = text("extended"); + // 操作数和布局在构造时校验,execute 期间测试实际值流与引用行为。 let code = make_code( &mut vm, &[op(Opcode::LoadConst, 256), simple(Opcode::ReturnValue)], constants, CodeLayout::default(), ); + // 借用已验证逻辑指令检查起始/末尾代码单元位置及最大栈高。 let executable = vm.payload::(code, "code").unwrap().code(); assert_eq!(executable.instructions()[0].offset, 0); assert_eq!(executable.instructions()[0].next_offset, 2); assert_eq!(executable.stack_size(), 1); + // 函数执行环境与名称表索引对应,字典内部边独立于测试外部根。 let globals = vm.new_dict().unwrap(); assert_eq!(vm.execute(code, globals).unwrap(), text("extended")); } #[test] +/// 空/截断/未知/前缀过多/无参带数据及跳入前缀中部在执行前拒绝。 fn malformed_wordcode_and_unaligned_jumps_are_rejected_at_construction() { + // 各非法物理流独立走 EndorCode 构造,均须返回 InvalidBytecode。 for bytes in [ vec![], vec![0], @@ -195,7 +242,8 @@ fn malformed_wordcode_and_unaligned_jumps_are_rejected_at_construction() { Err(VmError::InvalidBytecode { .. }) )); } - // Jump target 2 is inside a prefixed LOAD_CONST starting at code unit 1. + // 跳转目标 2 位于从代码单元 1 开始的扩展 LOAD_CONST 内部,不能作为逻辑起点。 + // 故意跳至带前缀 LOAD_CONST 的第二单元,而非该逻辑指令首前缀。 let bytes = vec![14, 1, 255, 1, 1, 0, 13, 0]; assert!(EndorCode::new(bytes, vec![Value::Nil; 257], vec![], vec![]).is_err()); assert!( @@ -209,7 +257,9 @@ fn malformed_wordcode_and_unaligned_jumps_are_rejected_at_construction() { } #[test] +/// 检查栈下溢、落出代码、变高回边、无 Cell 槽与分支汇合高度不一致。 fn verifier_rejects_underflow_fallthrough_bad_cells_and_inconsistent_stack_joins() { + // 每个逻辑指令序列隔离一种栈/控制流/槽布局错误。 let cases = [ vec![simple(Opcode::PopTop), simple(Opcode::ReturnValue)], vec![op(Opcode::LoadConst, 0)], @@ -225,12 +275,14 @@ fn verifier_rejects_underflow_fallthrough_bad_cells_and_inconsistent_stack_joins simple(Opcode::ReturnValue), ], ]; + // 相同常量和空布局下逐例验证,不能用执行时崩溃替代静态拒绝。 for instructions in cases { assert!( EndorCode::from_instructions(&instructions, vec![Value::Nil], CodeLayout::default()) .is_err() ); } + // 把局部 captured 明确标为 Cell,再用 LoadFast 证明访问操作码必须匹配槽种类。 let layout = CodeLayout { locals: vec!["captured".into()], cell_variables: vec!["captured".into()], @@ -247,10 +299,14 @@ fn verifier_rejects_underflow_fallthrough_bad_cells_and_inconsistent_stack_joins } #[test] +/// 验证 Load/StoreFast、全局存取和返回构成完整值流,成功不产生错误回溯。 fn execute_moves_values_between_constants_local_slots_and_globals() { + // 独立 VM 隔离对象计数/全局绑定,清理断言只针对本场景。 let mut vm = Vm::new().unwrap(); assert_eq!(vm.object_count(), 0); + // 函数执行环境与名称表索引对应,字典内部边独立于测试外部根。 let globals = vm.new_dict().unwrap(); + // 操作数和布局在构造时校验,execute 期间测试实际值流与引用行为。 let code = make_code( &mut vm, &[ @@ -274,20 +330,25 @@ fn execute_moves_values_between_constants_local_slots_and_globals() { Some(text("hello")) ); assert!(vm.last_traceback().is_empty()); + // 归还代码根同时移除常量表内部边,不重复释放常量对象的测试根。 vm.release(code).unwrap(); vm.release(globals).unwrap(); assert_eq!(vm.object_count(), 0); } #[derive(Debug)] +/// 驱动循环条件的无语言类型原生载荷,Arc 计数器由测试在 VM 外观察。 struct Tick(Arc); impl ObjectPayload for Tick { + /// Store 下转型识别测试载荷;与语言名义类型分开。 fn as_any(&self) -> &dyn Any { self } + /// 测试错误/诊断类别。 fn kind_name(&self) -> &'static str { "tick" } + /// Tick 旧式原生回调,前三次返回真、第四次为假,驱动可终止的字节码回边循环。 fn call(&self, arguments: &[Value]) -> Result { assert!(arguments.is_empty()); Ok(Value::Bool(self.0.fetch_add(1, Ordering::SeqCst) < 3)) @@ -295,13 +356,19 @@ impl ObjectPayload for Tick { } #[test] +/// 条件分支从指令末尾计算相对距离,回边重进 tick 调用,最后返回 done。 fn relative_branches_and_back_edges_execute_a_loop() { + // 独立 VM 隔离对象计数/全局绑定,清理断言只针对本场景。 let mut vm = Vm::new().unwrap(); + // VM 外原子计数用于确认循环调用次数,不通过返回文本猜测回边是否执行。 let count = Arc::new(AtomicUsize::new(0)); + // Tick 回调载荷持有 Rust Arc,返回直接 Bool,无托管捕获边。 let tick = vm.allocate(Tick(count.clone())).unwrap(); + // 函数执行环境与名称表索引对应,字典内部边独立于测试外部根。 let globals = vm.new_dict().unwrap(); vm.dict_set(globals, text("tick"), Value::Object(tick)) .unwrap(); + // 操作数和布局在构造时校验,execute 期间测试实际值流与引用行为。 let code = make_code( &mut vm, &[ -- 2.52.0 From bf3313f58d334178c24eebcc37fddea77c5d5eec Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 18:11:22 +0800 Subject: [PATCH 34/62] docs(core): explain nested call closure and execution cleanup regressions --- crates/core/tests/execution.rs | 93 ++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/crates/core/tests/execution.rs b/crates/core/tests/execution.rs index a093c2c..61b18d6 100644 --- a/crates/core/tests/execution.rs +++ b/crates/core/tests/execution.rs @@ -391,9 +391,13 @@ fn relative_branches_and_back_edges_execute_a_loop() { } #[test] +/// 验证直接默认参数和嵌套显式参数的对象结果均新增一份拥有根,归还后恢复原计数。 fn nested_language_calls_bind_defaults_and_return_owned_argument_objects() { + // 独立 VM 隔离调用帧、全局与根计数断言。 let mut vm = Vm::new().unwrap(); + // 函数执行环境的拥有根,函数也经载荷强边持有该字典。 let globals = vm.new_dict().unwrap(); + // 单参数源码函数只读参数再返回,默认值与显式实参应走相同拥有结果协议。 let identity = make_code( &mut vm, &[op(Opcode::LoadFast, 0), simple(Opcode::ReturnValue)], @@ -404,7 +408,9 @@ fn nested_language_calls_bind_defaults_and_return_owned_argument_objects() { ..CodeLayout::default() }, ); + // 默认字符串先有测试根,函数默认值表另持内部边。 let default = vm.new_string("default").unwrap(); + // 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。 let function = vm .new_function( "identity", @@ -414,15 +420,20 @@ fn nested_language_calls_bind_defaults_and_return_owned_argument_objects() { vec![], ) .unwrap(); + // 保存调用前计数,结果交付多一根,归还结果后必须恢复。 let before = vm.ref_count(default).unwrap(); + // 函数把已有参数/默认值作为结果,身份不变但结果仍取得独立拥有根。 let returned = vm.call(function, &[]).unwrap(); assert_eq!(returned, Value::Object(default)); assert_eq!(vm.ref_count(default).unwrap(), before + 1); + // 只归还本次调用结果根,不触碰默认表/常量/测试输入的所有权。 vm.release_value(returned).unwrap(); assert_eq!(vm.ref_count(default).unwrap(), before); vm.dict_set(globals, text("identity"), Value::Object(function)) .unwrap(); + // 显式实参放进包装代码常量表,也有独立测试根。 let argument = vm.new_string("argument").unwrap(); + // 包装指令按函数、Null、参数布局调用 identity,检验跨两个源码帧的引用转交。 let wrapper = make_code( &mut vm, &[ @@ -438,18 +449,25 @@ fn nested_language_calls_bind_defaults_and_return_owned_argument_objects() { ..CodeLayout::default() }, ); + // 保存调用前计数,结果交付多一根,归还结果后必须恢复。 let before = vm.ref_count(argument).unwrap(); + // 临时执行函数与嵌套帧清理后,结果对象仍须保持拥有。 let returned = vm.execute(wrapper, globals).unwrap(); assert_eq!(returned, Value::Object(argument)); assert_eq!(vm.ref_count(argument).unwrap(), before + 1); + // 只归还本次调用结果根,不触碰默认表/常量/测试输入的所有权。 vm.release_value(returned).unwrap(); assert_eq!(vm.ref_count(argument).unwrap(), before); } #[test] +/// 参数过少/过多及异堆参数均在帧提交前拒绝,函数根计数不得因失败增加。 fn argument_count_and_foreign_argument_failures_do_not_retain_inputs() { + // 独立 VM 隔离调用帧、全局与根计数断言。 let mut vm = Vm::new().unwrap(); + // 函数执行环境的拥有根,函数也经载荷强边持有该字典。 let globals = vm.new_dict().unwrap(); + // 手工已验证代码,操作数布局与后面的参数/自由变量元数据一一对应。 let code = make_code( &mut vm, &[op(Opcode::LoadFast, 0), simple(Opcode::ReturnValue)], @@ -460,6 +478,7 @@ fn argument_count_and_foreign_argument_failures_do_not_retain_inputs() { ..CodeLayout::default() }, ); + // 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。 let function = vm .new_function("identity", code, globals, vec![], vec![]) .unwrap(); @@ -479,8 +498,11 @@ fn argument_count_and_foreign_argument_failures_do_not_retain_inputs() { .. }) )); + // 另一堆产生真正有效但归属不同的参数,不能当本堆对象保留。 let mut other = Vm::new().unwrap(); + // 异堆参数在执行源码之前拒绝,不改变当前函数计数。 let foreign = other.new_object().unwrap(); + // 保存调用前计数,结果交付多一根,归还结果后必须恢复。 let before = vm.ref_count(function).unwrap(); assert_eq!( vm.call(function, &[Value::Object(foreign)]), @@ -490,10 +512,15 @@ fn argument_count_and_foreign_argument_failures_do_not_retain_inputs() { } #[test] +/// BuildMap 返回根在代码/全局释放后独立保活结果字典和子对象,释放结果才清空图。 fn freshly_built_maps_and_their_children_survive_frame_cleanup() { + // 独立 VM 隔离调用帧、全局与根计数断言。 let mut vm = Vm::new().unwrap(); + // 函数执行环境的拥有根,函数也经载荷强边持有该字典。 let globals = vm.new_dict().unwrap(); + // 子字符串先由测试根和代码常量持有,最终转由结果字典边保活。 let child = vm.new_string("owned by result").unwrap(); + // 手工已验证代码,操作数布局与后面的参数/自由变量元数据一一对应。 let code = make_code( &mut vm, &[ @@ -505,10 +532,15 @@ fn freshly_built_maps_and_their_children_survive_frame_cleanup() { vec![text("child"), Value::Object(child)], CodeLayout::default(), ); + // 归还测试根,代码常量在实际执行前仍维持子对象存活。 vm.release(child).unwrap(); + // 执行结果拥有根可跨越代码与全局生命周期。 let result = vm.execute(code, globals).unwrap(); + // 移除代码常量边后,返回图必须自己保留所需对象;不是只靠旧帧保活。 vm.release(code).unwrap(); + // 归还环境测试根,临时执行函数已清理,结果不能依赖其持续存在。 vm.release(globals).unwrap(); + // 结果字典只有一份外部根,其 child 值是独立内部强边。 let Value::Object(dictionary) = result else { panic!("expected a map"); }; @@ -518,10 +550,12 @@ fn freshly_built_maps_and_their_children_survive_frame_cleanup() { Some(Value::Object(child)) ); assert_eq!(vm.str(child).unwrap(), "owned by result"); + // 结果最后根归还,子对象的最后字典边随之释放,堆应归零。 vm.release(dictionary).unwrap(); assert_eq!(vm.object_count(), 0); } +/// 原生辅助只借用返回首参数,空参数错误;对象结果拥有根由 VM 补充。 fn echo(arguments: &[Value]) -> Result { arguments .first() @@ -530,15 +564,21 @@ fn echo(arguments: &[Value]) -> Result { } #[test] +/// 原生 echo 返回临时 BuildMap 实参,输入弹栈前必须先保留结果根。 fn native_return_of_a_temporary_argument_is_retained_before_inputs_are_popped() { + // 独立 VM 隔离调用帧、全局与根计数断言。 let mut vm = Vm::new().unwrap(); + // 函数执行环境的拥有根,函数也经载荷强边持有该字典。 let globals = vm.new_dict().unwrap(); + // 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。 let function = vm .allocate(EndorNativeFunctionObject::new("echo", echo)) .unwrap(); vm.dict_set(globals, text("echo"), Value::Object(function)) .unwrap(); + // 调用前/后按场景归还测试函数根;全局字典可能仍持有函数内部边。 vm.release(function).unwrap(); + // 手工已验证代码,操作数布局与后面的参数/自由变量元数据一一对应。 let code = make_code( &mut vm, &[ @@ -554,27 +594,36 @@ fn native_return_of_a_temporary_argument_is_retained_before_inputs_are_popped() ..CodeLayout::default() }, ); + // 执行结果拥有根可跨越代码与全局生命周期。 let result = vm.execute(code, globals).unwrap(); + // 移除代码常量边后,返回图必须自己保留所需对象;不是只靠旧帧保活。 vm.release(code).unwrap(); + // 归还环境测试根,临时执行函数已清理,结果不能依赖其持续存在。 vm.release(globals).unwrap(); assert_eq!(vm.object_count(), 1); vm.release_value(result).unwrap(); assert_eq!(vm.object_count(), 0); } +/// 原生辅助恒定报错,使执行器在 Call 处回溯与清理临时参数。 fn fail(_arguments: &[Value]) -> Result { Err(VmError::InvalidArgument("host failure")) } #[test] +/// 错误后对象数恢复调用前,回溯保留故障 Call 偏移及显式代码位置元数据。 fn native_failure_unwinds_temporary_roots_and_keeps_execution_locations() { + // 独立 VM 隔离调用帧、全局与根计数断言。 let mut vm = Vm::new().unwrap(); + // 函数执行环境的拥有根,函数也经载荷强边持有该字典。 let globals = vm.new_dict().unwrap(); + // 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。 let function = vm .allocate(EndorNativeFunctionObject::new("fail", fail)) .unwrap(); vm.dict_set(globals, text("fail"), Value::Object(function)) .unwrap(); + // 附显式名称、文件和起始行,用故障回溯断言实际保存的元数据。 let code = EndorCode::from_instructions( &[ op(Opcode::LoadGlobal, 0), @@ -591,7 +640,9 @@ fn native_failure_unwinds_temporary_roots_and_keeps_execution_locations() { ) .unwrap() .with_location("outer", "demo.endor", 12); + // 将已验证代码分配为拥有根,临时执行函数之后另外持代码边。 let code = vm.new_code(code).unwrap(); + // 记录代码/全局等场景根数,失败后不得残留临时字典、函数或帧根。 let before = vm.object_count(); assert_eq!( vm.execute(code, globals), @@ -605,9 +656,13 @@ fn native_failure_unwinds_temporary_roots_and_keeps_execution_locations() { } #[test] +/// 递归源码调用由显式帧向量推进,触及深度硬上限后全部执行根恢复,环境环可显式回收。 fn recursion_uses_explicit_frames_and_unwinds_at_the_frame_limit() { + // 独立 VM 隔离调用帧、全局与根计数断言。 let mut vm = Vm::new().unwrap(); + // 函数执行环境的拥有根,函数也经载荷强边持有该字典。 let globals = vm.new_dict().unwrap(); + // 手工已验证代码,操作数布局与后面的参数/自由变量元数据一一对应。 let code = make_code( &mut vm, &[ @@ -622,11 +677,13 @@ fn recursion_uses_explicit_frames_and_unwinds_at_the_frame_limit() { ..CodeLayout::default() }, ); + // 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。 let function = vm .new_function("recurse", code, globals, vec![], vec![]) .unwrap(); vm.dict_set(globals, text("recurse"), Value::Object(function)) .unwrap(); + // 递归深度会临时保留多份函数根,退出后应恢复精确初始计数。 let roots = vm.ref_count(function).unwrap(); assert_eq!( vm.call_with_limits( @@ -641,22 +698,30 @@ fn recursion_uses_explicit_frames_and_unwinds_at_the_frame_limit() { ); assert_eq!(vm.last_traceback().len(), 64); assert_eq!(vm.ref_count(function).unwrap(), roots); + // 调用前/后按场景归还测试函数根;全局字典可能仍持有函数内部边。 vm.release(function).unwrap(); + // 移除代码常量边后,返回图必须自己保留所需对象;不是只靠旧帧保活。 vm.release(code).unwrap(); + // 归还环境测试根,临时执行函数已清理,结果不能依赖其持续存在。 vm.release(globals).unwrap(); assert_eq!(vm.collect_cycles(), 3); } #[test] +/// 栈高合法的无限回边仍须由硬指令预算终止,不能泄漏临时函数或帧。 fn instruction_limit_stops_a_valid_infinite_loop_without_leaking_frames() { + // 独立 VM 隔离调用帧、全局与根计数断言。 let mut vm = Vm::new().unwrap(); + // 函数执行环境的拥有根,函数也经载荷强边持有该字典。 let globals = vm.new_dict().unwrap(); + // 手工已验证代码,操作数布局与后面的参数/自由变量元数据一一对应。 let code = make_code( &mut vm, &[op(Opcode::JumpBackward, 1)], vec![], CodeLayout::default(), ); + // 记录代码/全局等场景根数,失败后不得残留临时字典、函数或帧根。 let before = vm.object_count(); assert_eq!( vm.execute_with_limits( @@ -674,14 +739,19 @@ fn instruction_limit_stops_a_valid_infinite_loop_without_leaking_frames() { } #[test] +/// 函数捕获创建时内置字典身份,全局同名键仍优先,改写 __builtins__ 不重绑定旧函数。 fn function_globals_shadow_builtins_and_capture_the_original_builtins_dictionary() { + // 独立 VM 隔离调用帧、全局与根计数断言。 let mut vm = Vm::new().unwrap(); + // 函数执行环境的拥有根,函数也经载荷强边持有该字典。 let globals = vm.new_dict().unwrap(); + // 原内置字典由函数创建时捕获,不因稍后 globals.__builtins__ 改变而替换身份。 let builtins = vm.new_dict().unwrap(); vm.dict_set(builtins, text("answer"), text("builtin")) .unwrap(); vm.dict_set(globals, text("__builtins__"), Value::Object(builtins)) .unwrap(); + // 手工已验证代码,操作数布局与后面的参数/自由变量元数据一一对应。 let code = make_code( &mut vm, &[op(Opcode::LoadGlobal, 0), simple(Opcode::ReturnValue)], @@ -691,12 +761,15 @@ fn function_globals_shadow_builtins_and_capture_the_original_builtins_dictionary ..CodeLayout::default() }, ); + // 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。 let function = vm .new_function("read", code, globals, vec![], vec![]) .unwrap(); + // 新内置字典仅更新 globals 字段,用于证明旧函数仍引用原内置空间。 let replacement = vm.new_dict().unwrap(); vm.dict_set(globals, text("__builtins__"), Value::Object(replacement)) .unwrap(); + // 归还原内置测试根后仍应由函数 captured builtins 强边保活。 vm.release(builtins).unwrap(); assert_eq!(vm.call(function, &[]).unwrap(), text("builtin")); vm.dict_set(globals, text("answer"), text("global")) @@ -705,10 +778,15 @@ fn function_globals_shadow_builtins_and_capture_the_original_builtins_dictionary } #[test] +/// 多个函数共享同一 Cell,Load/Store/DeleteDeref 对内容变化立即可见,clear 后读取报未绑定。 fn shared_free_variable_cells_are_read_written_and_deleted_by_bytecode() { + // 独立 VM 隔离调用帧、全局与根计数断言。 let mut vm = Vm::new().unwrap(); + // 函数执行环境的拥有根,函数也经载荷强边持有该字典。 let globals = vm.new_dict().unwrap(); + // 共享槽初值 first,read/write/clear 函数各自持有同一 Cell 强边。 let cell = vm.new_cell(Some(text("first"))).unwrap(); + // 没有局部槽,唯一自由变量 Cell 位于 localsplus 索引零。 let read_code = make_code( &mut vm, &[op(Opcode::LoadDeref, 0), simple(Opcode::ReturnValue)], @@ -718,6 +796,7 @@ fn shared_free_variable_cells_are_read_written_and_deleted_by_bytecode() { ..CodeLayout::default() }, ); + // 有一个普通参数槽,外部 Cell 追加在索引一,StoreDeref 不能误写局部槽。 let write_code = make_code( &mut vm, &[ @@ -734,6 +813,7 @@ fn shared_free_variable_cells_are_read_written_and_deleted_by_bytecode() { ..CodeLayout::default() }, ); + // DeleteDeref 清空 Cell 而非写 Nil,随后返回直接 Nil。 let clear_code = make_code( &mut vm, &[ @@ -747,12 +827,15 @@ fn shared_free_variable_cells_are_read_written_and_deleted_by_bytecode() { ..CodeLayout::default() }, ); + // 只读捕获值的函数,共享槽未复制成独立快照。 let read = vm .new_function("read", read_code, globals, vec![], vec![cell]) .unwrap(); + // 把显式参数写到同一捕获 Cell,其他闭包应立即看到新值。 let write = vm .new_function("write", write_code, globals, vec![], vec![cell]) .unwrap(); + // 删除同一捕获变量后 read 映射 EmptyCell 为具名 UnboundLocal。 let clear = vm .new_function("clear", clear_code, globals, vec![], vec![cell]) .unwrap(); @@ -764,9 +847,13 @@ fn shared_free_variable_cells_are_read_written_and_deleted_by_bytecode() { } #[test] +/// 参数提升为 Cell 后通过 LoadClosure 返回,原帧与参数测试根释放仍由 Cell 边保活内容。 fn captured_argument_cells_keep_their_contents_after_the_frame_returns() { + // 独立 VM 隔离调用帧、全局与根计数断言。 let mut vm = Vm::new().unwrap(); + // 函数执行环境的拥有根,函数也经载荷强边持有该字典。 let globals = vm.new_dict().unwrap(); + // 手工已验证代码,操作数布局与后面的参数/自由变量元数据一一对应。 let code = make_code( &mut vm, &[op(Opcode::LoadClosure, 0), simple(Opcode::ReturnValue)], @@ -778,17 +865,23 @@ fn captured_argument_cells_keep_their_contents_after_the_frame_returns() { ..CodeLayout::default() }, ); + // 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。 let function = vm .new_function("capture", code, globals, vec![], vec![]) .unwrap(); + // 参数对象有测试根,帧初始化提升 Cell 时另建立内容强边。 let value = vm.new_string("captured").unwrap(); + // 函数把已有参数/默认值作为结果,身份不变但结果仍取得独立拥有根。 let returned = vm.call(function, &[Value::Object(value)]).unwrap(); + // 归还原参数测试根,返回的 Cell 内容边仍须保活它。 vm.release(value).unwrap(); + // LoadClosure 返回的是 Cell 本身,不是 Cell 当前内容。 let Value::Object(cell) = returned else { panic!("expected cell"); }; assert_eq!(vm.cell_get(cell).unwrap(), Value::Object(value)); assert_eq!(vm.str(value).unwrap(), "captured"); + // 最后 Cell 结果根归还后其内容边释放,原字符串身份应立即失效。 vm.release(cell).unwrap(); assert!(matches!(vm.object(value), Err(VmError::InvalidHandle))); } -- 2.52.0 From 7d14bce9f37928cc31fb7ba98c3fca44370bb293 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 18:12:45 +0800 Subject: [PATCH 35/62] docs(core): explain attribute stack and function factory regressions --- crates/core/tests/execution.rs | 68 +++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/crates/core/tests/execution.rs b/crates/core/tests/execution.rs index 61b18d6..4905aaf 100644 --- a/crates/core/tests/execution.rs +++ b/crates/core/tests/execution.rs @@ -887,9 +887,13 @@ fn captured_argument_cells_keep_their_contents_after_the_frame_returns() { } #[test] +/// StoreAttr/LoadAttr 与 Copy/Swap/PopTop 组合后仅保留宿主、属性和结果三份根,不残留复制栈根。 fn attribute_operations_and_stack_copies_transfer_roots_without_leaks() { + // 独立 VM 隔离堆计数和全局/回溯状态。 let mut vm = Vm::new().unwrap(); + // 测试执行环境本身有根,函数可能另持全局边形成待收集环。 let globals = vm.new_dict().unwrap(); + // 手工已验证指令,按下面布局解释局部槽和 names 索引。 let code = make_code( &mut vm, &[ @@ -911,24 +915,34 @@ fn attribute_operations_and_stack_copies_transfer_roots_without_leaks() { ..CodeLayout::default() }, ); + // 源码/原生函数有一份测试根,调用时另行保留帧/协议根。 let function = vm .new_function("assign", code, globals, vec![], vec![]) .unwrap(); + // 属性写入目标拥有测试根,字段还会独立持有 value 边。 let record = vm.new_object().unwrap(); + // 原值测试根保留到最后,调用返回和属性边应各增加一份持有。 let value = vm.new_string("value").unwrap(); + // 返回对象必须独立拥有,栈 Copy 产生的额外根应已被 PopTop 清理。 let returned = vm .call(function, &[Value::Object(record), Value::Object(value)]) .unwrap(); assert_eq!(vm.ref_count(value).unwrap(), 3); // host, attribute, return value + // 先归还调用结果根,再释放 record 去掉字段边,value 最终只剩测试根。 vm.release_value(returned).unwrap(); + // 属性 owner 最后根消失,其字段拥有边随对象释放。 vm.release(record).unwrap(); assert_eq!(vm.ref_count(value).unwrap(), 1); } #[test] +/// 文本比较经 ToBool/UnaryNot 后控制前向分支,检验跳转基点与运行时真值。 fn comparison_truth_and_forward_conditionals_use_runtime_values() { + // 独立 VM 隔离堆计数和全局/回溯状态。 let mut vm = Vm::new().unwrap(); + // 测试执行环境本身有根,函数可能另持全局边形成待收集环。 let globals = vm.new_dict().unwrap(); + // 手工已验证指令,按下面布局解释局部槽和 names 索引。 let code = make_code( &mut vm, &[ @@ -950,9 +964,13 @@ fn comparison_truth_and_forward_conditionals_use_runtime_values() { } #[test] +/// 缺全局、删除局部后读取和 Null 逃逸分别报错,随后成功执行应清掉旧回溯。 fn unbound_names_deleted_locals_and_invalid_null_values_fail_cleanly() { + // 独立 VM 隔离堆计数和全局/回溯状态。 let mut vm = Vm::new().unwrap(); + // 测试执行环境本身有根,函数可能另持全局边形成待收集环。 let globals = vm.new_dict().unwrap(); + // 名称表合法但环境无该键,属于执行期 NameNotFound 而非索引验证错误。 let missing = make_code( &mut vm, &[op(Opcode::LoadGlobal, 0), simple(Opcode::ReturnValue)], @@ -966,6 +984,7 @@ fn unbound_names_deleted_locals_and_invalid_null_values_fail_cleanly() { vm.execute(missing, globals), Err(VmError::NameNotFound("missing".into())) ); + // 先成功赋值再 DeleteFast,最后读取检验 None 未绑定状态。 let deleted = make_code( &mut vm, &[ @@ -985,6 +1004,7 @@ fn unbound_names_deleted_locals_and_invalid_null_values_fail_cleanly() { vm.execute(deleted, globals), Err(VmError::UnboundLocal("x".into())) ); + // PushNull 满足抽象栈高度但不是语言 Value,ReturnValue 必须在运行时拒绝。 let null = make_code( &mut vm, &[simple(Opcode::PushNull), simple(Opcode::ReturnValue)], @@ -995,15 +1015,20 @@ fn unbound_names_deleted_locals_and_invalid_null_values_fail_cleanly() { vm.execute(null, globals), Err(VmError::InvalidBytecode { .. }) )); + // 错误后执行简单成功代码,验证 last_traceback 不残留前一次故障。 let success = return_constant(&mut vm, Value::Nil); assert_eq!(vm.execute(success, globals).unwrap(), Value::Nil); assert!(vm.last_traceback().is_empty()); } #[test] +/// 不可哈希数值键使外层建图失败,半成品和作为值的临时空图都须被清理。 fn failed_map_construction_cleans_both_partial_map_and_temporary_input() { + // 独立 VM 隔离堆计数和全局/回溯状态。 let mut vm = Vm::new().unwrap(); + // 测试执行环境本身有根,函数可能另持全局边形成待收集环。 let globals = vm.new_dict().unwrap(); + // 手工已验证指令,按下面布局解释局部槽和 names 索引。 let code = make_code( &mut vm, &[ @@ -1015,6 +1040,7 @@ fn failed_map_construction_cleans_both_partial_map_and_temporary_input() { vec![Value::Number(1_i64.into())], CodeLayout::default(), ); + // 失败前存活对象基线,执行临时函数和两层临时图不得泄漏。 let before = vm.object_count(); assert_eq!( vm.execute(code, globals), @@ -1024,12 +1050,17 @@ fn failed_map_construction_cleans_both_partial_map_and_temporary_input() { } #[test] +/// 调用协议位置的 Value::Nil 是真实隐式参数,不得像专用 Null 标记那样被忽略。 fn implicit_receiver_slot_is_distinct_from_the_null_call_marker() { + // 独立 VM 隔离堆计数和全局/回溯状态。 let mut vm = Vm::new().unwrap(); + // 测试执行环境本身有根,函数可能另持全局边形成待收集环。 let globals = vm.new_dict().unwrap(); + // 源码/原生函数有一份测试根,调用时另行保留帧/协议根。 let function = vm .allocate(EndorNativeFunctionObject::new("echo", echo)) .unwrap(); + // 手工已验证指令,按下面布局解释局部槽和 names 索引。 let code = make_code( &mut vm, &[ @@ -1041,11 +1072,12 @@ fn implicit_receiver_slot_is_distinct_from_the_null_call_marker() { vec![Value::Object(function), Value::Nil], CodeLayout::default(), ); - // Nil is an actual implicit argument here, not the internal NULL marker. + // 此处 Nil 会作为真实隐式参数传入 echo,只有 StackEntry::Null 才省略协议位置。 assert_eq!(vm.execute(code, globals).unwrap(), Value::Nil); } #[test] +/// 验证默认值/闭包计数打包编码和边界值;构造校验要求完整输入栈高,但不验证运行时载荷种类。 fn function_creation_operands_round_trip_and_require_all_inputs() { assert_eq!( Instruction::encode_all(&[Instruction::make_function(1, 1)]).unwrap(), @@ -1055,6 +1087,7 @@ fn function_creation_operands_round_trip_and_require_all_inputs() { Instruction::encode_all(&[Instruction::make_function(u16::MAX, u16::MAX)]).unwrap(), vec![255, 255, 255, 255, 255, 255, 28, 255] ); + // 这里只验证 MakeFunction 逻辑输入数量/编码,常量 Nil 占位并不实际执行构造。 let code = EndorCode::from_instructions( &[ op(Opcode::LoadConst, 0), @@ -1073,6 +1106,7 @@ fn function_creation_operands_round_trip_and_require_all_inputs() { ); assert_eq!(code.instructions()[3].next_offset, 6); assert_eq!(code.stack_size(), 3); + // 不同计数组合都在空栈上缺少代码对象项,必须构造阶段报栈下溢。 for (defaults, closure) in [(0, 0), (1, 0), (0, 1), (1, 1), (u16::MAX, u16::MAX)] { assert!(matches!( EndorCode::from_instructions( @@ -1086,6 +1120,7 @@ fn function_creation_operands_round_trip_and_require_all_inputs() { Err(VmError::InvalidBytecode { .. }) )); } + // 只有一个常量不能满足含默认值或捕获槽的函数构造,缺输入仍须静态拒绝。 for creation in [ Instruction::make_function(1, 0), Instruction::make_function(0, 1), @@ -1106,9 +1141,13 @@ fn function_creation_operands_round_trip_and_require_all_inputs() { } #[test] +/// MakeFunction 在每次模块执行创建新函数身份,代码可共享,默认值/显式实参都能调用。 fn bytecode_defines_global_functions_and_calls_them_with_defaults() { + // 独立 VM 隔离堆计数和全局/回溯状态。 let mut vm = Vm::new().unwrap(); + // 测试执行环境本身有根,函数可能另持全局边形成待收集环。 let globals = vm.new_dict().unwrap(); + // 内部 identity 的参数读取直接返回,代码诊断位置给新函数命名。 let inner = EndorCode::from_instructions( &[op(Opcode::LoadFast, 0), simple(Opcode::ReturnValue)], vec![], @@ -1120,7 +1159,9 @@ fn bytecode_defines_global_functions_and_calls_them_with_defaults() { ) .unwrap() .with_location("identity", "functions.endor", 2); + // 代码常量由模块/工厂保留,归还测试根后仍用于后续 MakeFunction。 let inner = vm.new_code(inner).unwrap(); + // 按默认值、代码顺序压栈创建函数,存全局后再 Call 零显式参数走默认值。 let module = make_code( &mut vm, &[ @@ -1139,12 +1180,16 @@ fn bytecode_defines_global_functions_and_calls_them_with_defaults() { ..CodeLayout::default() }, ); + // 归还内层代码测试根;外层代码常量边接管保活,不能仅靠裸句柄继续使用。 vm.release(inner).unwrap(); assert_eq!(vm.execute(module, globals).unwrap(), text("default")); + // 全局读取返回借用函数身份,要跨后续重定义存活必须显式 retain。 let Some(Value::Object(first)) = vm.dict_get(globals, &text("identity")).unwrap() else { panic!("expected a function"); }; + // 保留旧函数独立根,模块再次执行替换全局后还可比较它与新函数身份。 vm.retain(first).unwrap(); + // 借用函数元数据确认 name/code/globals 来自创建上下文。 let data = vm .payload::(first, "function") .unwrap(); @@ -1156,6 +1201,7 @@ fn bytecode_defines_global_functions_and_calls_them_with_defaults() { text("explicit") ); assert_eq!(vm.execute(module, globals).unwrap(), text("default")); + // 再次执行得到不同函数对象,但两者仍共享同一个代码对象。 let Some(Value::Object(second)) = vm.dict_get(globals, &text("identity")).unwrap() else { panic!("expected a second function"); }; @@ -1166,18 +1212,25 @@ fn bytecode_defines_global_functions_and_calls_them_with_defaults() { .code(), inner ); + // 归还为跨重定义而取得的旧函数根,不能把借用全局结果当作拥有根。 vm.release(first).unwrap(); + // 模块代码常量边释放,仍由全局函数持有内部代码。 vm.release(module).unwrap(); + // 归还环境测试根,若函数与环境互相持有则需显式环回收。 vm.release(globals).unwrap(); - // The global function, its code and its globals form a collectible cycle. + // 全局字典持有函数、函数持有全局和代码,失去外部根后形成三个对象的可回收图。 assert_eq!(vm.collect_cycles(), 3); assert_eq!(vm.object_count(), 0); } #[test] +/// 工厂创建临时默认字典,函数返回后默认值仍由函数边保活,每次调用返回同一默认对象的独立根。 fn bytecode_function_defaults_keep_temporary_objects_alive_after_factory_returns() { + // 独立 VM 隔离堆计数和全局/回溯状态。 let mut vm = Vm::new().unwrap(); + // 测试执行环境本身有根,函数可能另持全局边形成待收集环。 let globals = vm.new_dict().unwrap(); + // 内部函数有一个必填与两个默认参数,返回中间 defaulted 槽。 let inner = make_code( &mut vm, &[op(Opcode::LoadFast, 1), simple(Opcode::ReturnValue)], @@ -1188,6 +1241,7 @@ fn bytecode_function_defaults_keep_temporary_objects_alive_after_factory_returns ..CodeLayout::default() }, ); + // 先在栈上新建默认空图,再压 tag 和代码,MakeFunction 把前两项作为默认值。 let factory = make_code( &mut vm, &[ @@ -1200,26 +1254,36 @@ fn bytecode_function_defaults_keep_temporary_objects_alive_after_factory_returns vec![Value::Object(inner), text("tag")], CodeLayout::default(), ); + // 归还内层代码测试根;外层代码常量边接管保活,不能仅靠裸句柄继续使用。 vm.release(inner).unwrap(); + // 工厂结果函数拥有一根,默认字典由其 defaults 强边持有。 let Value::Object(function) = vm.execute(factory, globals).unwrap() else { panic!("expected a function"); }; + // 工厂帧和代码释放后,结果函数必须独立保留代码/环境/默认图。 vm.release(factory).unwrap(); + // 归还环境测试根,若函数与环境互相持有则需显式环回收。 vm.release(globals).unwrap(); assert!(matches!( vm.call(function, &[]), Err(VmError::ArgumentCount { required: 1, .. }) )); + // 第一次默认调用返回默认字典对象,需独立归还调用结果根。 let first = vm.call(function, &[Value::Nil]).unwrap(); + // 第二次返回同一默认对象,默认表达式不应每次重新求值创建新图。 let second = vm.call(function, &[Value::Nil]).unwrap(); assert_eq!(first, second); // Defaults are evaluated once per creation. + // 默认图此时有函数默认边加两份结果根,共三份强引用。 let Value::Object(default) = first else { panic!("expected a default map"); }; assert_eq!(vm.ref_count(default).unwrap(), 3); // Function and two results. + // 释放函数与默认值内部边,两个结果根仍使默认图存活。 vm.release(function).unwrap(); + // 仅归还第一调用结果根,第二结果仍独立拥有一根。 vm.release_value(first).unwrap(); assert_eq!(vm.ref_count(default).unwrap(), 1); + // 最后结果根归还,默认图及其余无根对象应全部释放。 vm.release_value(second).unwrap(); assert_eq!(vm.object_count(), 0); } -- 2.52.0 From 28b305b503b355c633227492638928f94e5a9d2c Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 18:17:31 +0800 Subject: [PATCH 36/62] docs(core): explain closure roots and coroutine cleanup tests --- crates/core/tests/execution.rs | 122 +++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/crates/core/tests/execution.rs b/crates/core/tests/execution.rs index 4905aaf..3533ecb 100644 --- a/crates/core/tests/execution.rs +++ b/crates/core/tests/execution.rs @@ -1289,9 +1289,13 @@ fn bytecode_function_defaults_keep_temporary_objects_alive_after_factory_returns } #[test] +/// 由 Rust 测试框架调用:外层参数提升为两个 Cell,两个新函数共享同一捕获列表,外层清理后仍可读写并使用创建时默认值。 fn bytecode_nested_functions_share_cells_and_bind_defaults_after_outer_returns() { + // 独立 VM 隔离本场景根数、全局绑定与暂停状态。 let mut vm = Vm::new().unwrap(); + // 为当前测试建立独立全局环境;测试持初始根,函数可另外持环境内部边。 let globals = vm.new_dict().unwrap(); + // 两个外部 Cell 从索引零开始,read 只读取 x,不修改捕获列表。 let read = make_code( &mut vm, &[op(Opcode::LoadDeref, 0), simple(Opcode::ReturnValue)], @@ -1301,6 +1305,7 @@ fn bytecode_nested_functions_share_cells_and_bind_defaults_after_outer_returns() ..CodeLayout::default() }, ); + // 有一个普通参数槽,StoreDeref 索引一对应 x,sentinel 在索引二保持未改。 let write = make_code( &mut vm, &[ @@ -1317,6 +1322,7 @@ fn bytecode_nested_functions_share_cells_and_bind_defaults_after_outer_returns() ..CodeLayout::default() }, ); + // 栈按键、捕获 Cell、代码构造两个函数,write 默认值在 Cell 段之前。 let outer = make_code( &mut vm, &[ @@ -1348,21 +1354,26 @@ fn bytecode_nested_functions_share_cells_and_bind_defaults_after_outer_returns() ..CodeLayout::default() }, ); + // 外层源码函数参数 x/sentinel 提升共享槽,函数返回 dict 持两个内层函数。 let outer_function = vm .new_function("outer", outer, globals, vec![], vec![]) .unwrap(); + // 函数对结果拥有一根,后续可独立于外层函数和原代码根存活。 let Value::Object(pair) = vm .call(outer_function, &[text("first"), text("sentinel")]) .unwrap() else { panic!("expected a function pair"); }; + // 归还外层及三个代码对象的测试根,只留下结果图拥有边保活闭包链。 for reference in [read, write, outer, outer_function, globals] { vm.release(reference).unwrap(); } + // dict_get 返回借用函数,pair 根一直保持,因此这里不单独 release 函数。 let Some(Value::Object(read)) = vm.dict_get(pair, &text("read")).unwrap() else { panic!("expected read"); }; + // 另一个闭包借用句柄,closure 列表应与 read 身份逐项相同。 let Some(Value::Object(write)) = vm.dict_get(pair, &text("write")).unwrap() else { panic!("expected write"); }; @@ -1380,15 +1391,21 @@ fn bytecode_nested_functions_share_cells_and_bind_defaults_after_outer_returns() assert_eq!(vm.call(read, &[]).unwrap(), text("updated")); assert_eq!(vm.call(write, &[]).unwrap(), text("default")); assert_eq!(vm.call(read, &[]).unwrap(), text("default")); + // 最后函数对根归还后,内层函数、Cell、代码和环境图一起释放。 vm.release(pair).unwrap(); assert_eq!(vm.object_count(), 0); } #[test] +/// 由 Rust 测试框架调用:MakeFunction 运行时拒绝代码/Cell 种类错误、布局数量不符及 Null,失败后临时图与根计数全部恢复。 fn bytecode_function_creation_rejects_bad_inputs_without_leaking_roots() { + // 独立 VM 隔离本场景根数、全局绑定与暂停状态。 let mut vm = Vm::new().unwrap(); + // 为当前测试建立独立全局环境;测试持初始根,函数可另外持环境内部边。 let globals = vm.new_dict().unwrap(); + // 无参数/自由变量代码,作为多余默认值或多余 Cell 的数量错误基线。 let plain = return_constant(&mut vm, Value::Nil); + // 要求一个外部 Cell 的代码,配合不同错误栈值验证捕获种类/数量。 let captured = make_code( &mut vm, &[op(Opcode::LoadDeref, 0), simple(Opcode::ReturnValue)], @@ -1398,8 +1415,11 @@ fn bytecode_function_creation_rejects_bad_inputs_without_leaking_roots() { ..CodeLayout::default() }, ); + // 有效但未初始化的 Cell,数量不符不能因它是合法 Cell 而被放过。 let cell = vm.new_cell(None).unwrap(); + // 函数默认值和闭包布局不匹配的统一错误,不与载荷种类错误混淆。 let mismatch = VmError::InvalidArgument("function defaults or closure do not match code"); + // 分别覆盖非对象代码、错误对象代码、Null、默认/捕获数量不符和无效 Cell 输入。 let cases = [ ( vec![op(Opcode::LoadConst, 0), Instruction::make_function(0, 0)], @@ -1476,13 +1496,20 @@ fn bytecode_function_creation_rejects_bad_inputs_without_leaking_roots() { invalid_null(), ), ]; + // 每个案例追加合法 Return,确保失败来自函数创建而非缺返回。 for (mut instructions, constants, expected) in cases { + // 补全出口,make_code 仍先验证栈数量,运行再校验值的真实种类。 instructions.push(simple(Opcode::ReturnValue)); + // 为当前错误案例单独分配代码根,执行后归还以隔离计数断言。 let code = make_code(&mut vm, &instructions, constants, CodeLayout::default()); + // 记录含代码与常量边的当前对象数,执行失败不能残留新函数/默认图。 let count = vm.object_count(); + // 记录所有已有代码/Cell/环境强计数,失败不得额外 retain 输入。 let roots = [plain, captured, cell, globals].map(|reference| vm.ref_count(reference).unwrap()); + // 必须实际执行到 MakeFunction 才检验值种类,这不是构造时仅栈高验证能替代的检查。 let error = vm.execute(code, globals).unwrap_err(); + // Null 类错误比较类别和原因,不固定各案例的实际指令偏移。 if matches!(expected, VmError::InvalidBytecode { .. }) { assert!(matches!( error, @@ -1499,14 +1526,17 @@ fn bytecode_function_creation_rejects_bad_inputs_without_leaking_roots() { [plain, captured, cell, globals].map(|reference| vm.ref_count(reference).unwrap()), roots ); + // 归还当前代码的测试根;仍被函数或帧引用的代码由内部边保活。 vm.release(code).unwrap(); } + // 全部案例结束再归还共享输入根,内部边由 Heap 自动清理。 for reference in [plain, captured, cell, globals] { vm.release(reference).unwrap(); } assert_eq!(vm.object_count(), 0); } +/// 错误用例辅助,仅提供 Null 非法值的类别/原因,实际偏移随各指令序列位置变化。 fn invalid_null() -> VmError { VmError::InvalidBytecode { offset: 0, @@ -1515,10 +1545,15 @@ fn invalid_null() -> VmError { } #[test] +/// 由 Rust 测试框架调用:同一工厂两次创建的函数捕获不同内置字典,但共享实时全局字典,全局值优先于各自内置。 fn bytecode_functions_capture_builtins_at_creation_and_share_live_globals() { + // 独立 VM 隔离本场景根数、全局绑定与暂停状态。 let mut vm = Vm::new().unwrap(); + // 为当前测试建立独立全局环境;测试持初始根,函数可另外持环境内部边。 let globals = vm.new_dict().unwrap(); + // 第一次函数创建时捕获的内置字典,内容为 original。 let original = vm.new_dict().unwrap(); + // 改写 __builtins__ 后第二次函数创建捕获此字典,旧函数不重绑定。 let replacement = vm.new_dict().unwrap(); vm.dict_set(original, text("answer"), text("original")) .unwrap(); @@ -1526,6 +1561,7 @@ fn bytecode_functions_capture_builtins_at_creation_and_share_live_globals() { .unwrap(); vm.dict_set(globals, text("__builtins__"), Value::Object(original)) .unwrap(); + // 内部代码由工厂持有,作为 MakeFunction 最后一个栈输入。 let inner = make_code( &mut vm, &[op(Opcode::LoadGlobal, 0), simple(Opcode::ReturnValue)], @@ -1535,6 +1571,7 @@ fn bytecode_functions_capture_builtins_at_creation_and_share_live_globals() { ..CodeLayout::default() }, ); + // 工厂字节码使用函数创建协议,输入根保留至新函数成功持有所有边。 let factory = make_code( &mut vm, &[ @@ -1545,14 +1582,17 @@ fn bytecode_functions_capture_builtins_at_creation_and_share_live_globals() { vec![Value::Object(inner)], CodeLayout::default(), ); + // 第一次创建产生独立函数拥有根,其 captured builtins 为原字典。 let Value::Object(first) = vm.execute(factory, globals).unwrap() else { panic!("expected first function"); }; vm.dict_set(globals, text("__builtins__"), Value::Object(replacement)) .unwrap(); + // 第二次创建捕获新字典,但 globals 身份仍与第一函数相同。 let Value::Object(second) = vm.execute(factory, globals).unwrap() else { panic!("expected second function"); }; + // 归还两个内置字典和工厂/代码/环境测试根,两份函数结果仍须完整保活依赖图。 for reference in [original, replacement, factory, inner, globals] { vm.release(reference).unwrap(); } @@ -1568,9 +1608,13 @@ fn bytecode_functions_capture_builtins_at_creation_and_share_live_globals() { } #[test] +/// 由 Rust 测试框架调用:函数和临时默认图已成功创建后,后续原生调用失败仍须由帧清理释放两者。 fn later_execution_failure_releases_a_new_function_and_its_temporary_default() { + // 独立 VM 隔离本场景根数、全局绑定与暂停状态。 let mut vm = Vm::new().unwrap(); + // 为当前测试建立独立全局环境;测试持初始根,函数可另外持环境内部边。 let globals = vm.new_dict().unwrap(); + // 内部代码由工厂持有,作为 MakeFunction 最后一个栈输入。 let inner = make_code( &mut vm, &[op(Opcode::LoadFast, 0), simple(Opcode::ReturnValue)], @@ -1581,9 +1625,11 @@ fn later_execution_failure_releases_a_new_function_and_its_temporary_default() { ..CodeLayout::default() }, ); + // 原生失败回调在函数和默认图已创建后才进入,验证后续错误的整体清理。 let fail = vm .allocate(EndorNativeFunctionObject::new("fail", fail)) .unwrap(); + // 工厂字节码使用函数创建协议,输入根保留至新函数成功持有所有边。 let factory = make_code( &mut vm, &[ @@ -1598,8 +1644,11 @@ fn later_execution_failure_releases_a_new_function_and_its_temporary_default() { vec![Value::Object(fail), Value::Object(inner)], CodeLayout::default(), ); + // 回调原测试根归还后由工厂代码常量保活。 vm.release(fail).unwrap(); + // 代码原测试根归还后由工厂常量/新函数内部边接管。 vm.release(inner).unwrap(); + // 记录含代码与常量边的当前对象数,执行失败不能残留新函数/默认图。 let count = vm.object_count(); assert_eq!( vm.execute(factory, globals), @@ -1607,29 +1656,38 @@ fn later_execution_failure_releases_a_new_function_and_its_temporary_default() { ); assert_eq!(vm.object_count(), count); vm.release(factory).unwrap(); + // 归还环境测试根,剩余函数内部边决定环境是否继续存活。 vm.release(globals).unwrap(); assert_eq!(vm.object_count(), 0); } #[test] +/// 由 Rust 测试框架调用:只读切片停放仍持函数/常量图,宿主根全归还并 GC 后可恢复完成,结果归还后堆为空。 fn suspended_read_only_call_keeps_roots_and_releases_them_on_completion() { use endor_core::ExecutionProgress; + // 独立 VM 隔离本场景根数、全局绑定与暂停状态。 let mut vm = Vm::new().unwrap(); + // 调用前已存在输入,进入只读快照后禁止修改;也是最终返回的对象。 let shared = vm.new_object().unwrap(); vm.set_attr(shared, "value", Value::Number(7_i64.into())) .unwrap(); + // 为当前测试建立独立全局环境;测试持初始根,函数可另外持环境内部边。 let globals = vm.new_dict().unwrap(); + // 先放十六条 Nop 强制跨多个量子,然后装载 shared 返回,共十八条源码指令。 let mut instructions = vec![simple(Opcode::Nop); 16]; instructions.extend([op(Opcode::LoadConst, 0), simple(Opcode::ReturnValue)]); + // 常量表保留 shared;切片帧又经函数到代码图保活常量。 let code = make_code( &mut vm, &instructions, vec![Value::Object(shared)], CodeLayout::default(), ); + // 源码函数拥有代码/环境边,停放帧会另外保留函数执行根。 let function = vm .new_function("sliced", code, globals, vec![], vec![]) .unwrap(); + // 首量子只执行一条 Nop,必须 Pending 而不是重跑准备或提前完成。 let mut progress = vm .start_read_only_call( function, @@ -1650,12 +1708,19 @@ fn suspended_read_only_call_keeps_roots_and_releases_them_on_completion() { .is_err() ); assert!(vm.call(function, &[]).is_err()); + // 宿主可归还函数原根,停放帧自己的根仍需保证之后恢复有效。 vm.release(function).unwrap(); + // 归还当前代码的测试根;仍被函数或帧引用的代码由内部边保活。 vm.release(code).unwrap(); + // 归还环境测试根,剩余函数内部边决定环境是否继续存活。 vm.release(globals).unwrap(); + // 归还输入原根后,代码常量仍保护输入,最终结果再接管自己的根。 vm.release(shared).unwrap(); + // 暂停期间强制 GC 不能回收帧执行根可达图,检验保活不依赖宿主残留根。 vm.collect_cycles(); + // 反复恢复同一帧而非重跑函数,累计计数最终必须十八。 loop { + // Pending 只推进下一量子,Complete 负责读取拥有结果并归还。 match progress { ExecutionProgress::Pending { .. } => progress = vm.resume_read_only_call(2).unwrap(), ExecutionProgress::Complete { @@ -1669,6 +1734,7 @@ fn suspended_read_only_call_keeps_roots_and_releases_them_on_completion() { vm.get_attr(shared, "value").unwrap(), Value::Number(7_i64.into()) ); + // 最后结果根归还后,暂停帧/函数等已清理,输入也应不再有残余边。 vm.release_value(value).unwrap(); break; } @@ -1679,22 +1745,31 @@ fn suspended_read_only_call_keeps_roots_and_releases_them_on_completion() { } #[test] +/// 由 Rust 测试框架调用,验证只读切片的累计预算失败与主动取消都会清理执行根、恢复写权限;无效量子保留暂停状态。 fn suspended_call_cancellation_and_total_budget_restore_read_only_state() { + // 隔离对象堆与执行状态,使最终零对象断言只覆盖当前场景。 let mut vm = Vm::new().unwrap(); + // 建立调用前对象;执行结束或取消后写它可验证只读状态已经退出。 let shared = vm.new_object().unwrap(); + // 全局字典由测试持初始根,函数和协程图另有内部引用。 let globals = vm.new_dict().unwrap(); + // 八条 Nop 加载入与返回共十条指令,保证总预算三条时尚未正常结束。 let mut instructions = vec![simple(Opcode::Nop); 8]; instructions.extend([op(Opcode::LoadConst, 0), simple(Opcode::ReturnValue)]); + // 为当前场景分配已验证代码,常量及名字表由代码对象持有。 let code = make_code( &mut vm, &instructions, vec![Value::Object(shared)], CodeLayout::default(), ); + // 函数持有代码和全局环境,后续协程或暂停帧再持有函数。 let function = vm .new_function("budget", code, globals, vec![], vec![]) .unwrap(); + // 记录进入切片前的函数计数;预算错误和取消后都应恢复此计数。 let roots = vm.ref_count(function).unwrap(); + // 首量子执行一条;后续恢复沿用同一总预算,不重新开始计数。 vm.start_read_only_call( function, &[], @@ -1705,34 +1780,50 @@ fn suspended_call_cancellation_and_total_budget_restore_read_only_state() { 1, ) .unwrap(); + // 零量子属于调用参数错误,不能清除仍然有效的暂停调用。 assert!(vm.resume_read_only_call(0).is_err()); assert!(vm.has_suspended_call()); + // 第二条指令推进成功,第三条达到硬上限时仍无法到达返回。 vm.resume_read_only_call(1).unwrap(); assert_eq!( vm.resume_read_only_call(1).unwrap_err(), VmError::ExecutionLimit("instructions") ); + // 预算耗尽已经终止调用,不能残留可恢复帧。 assert!(!vm.has_suspended_call()); assert_eq!(vm.ref_count(function).unwrap(), roots); + // 预算失败后修改旧对象成功,证明退出了只读保护范围。 vm.set_attr(shared, "after", Value::Bool(true)).unwrap(); + // 使用默认总预算新建另一调用,专门验证主动取消后的状态恢复。 vm.start_read_only_call(function, &[], ExecutionLimits::default(), 1) .unwrap(); + // 第一次取消释放执行根并报告确实取消了任务。 assert!(vm.cancel_read_only_call().unwrap()); + // 第二次取消没有任务可处理,必须安全返回 false。 assert!(!vm.cancel_read_only_call().unwrap()); assert_eq!(vm.ref_count(function).unwrap(), roots); + // 主动取消也恢复旧对象写权限,不能仅清理帧而遗留只读状态。 vm.set_attr(shared, "after", Value::Bool(false)).unwrap(); + // 归还函数测试根,函数内部边随对象释放而清理。 vm.release(function).unwrap(); + // 归还代码测试根,常量拥有边随代码销毁而归还。 vm.release(code).unwrap(); + // 归还全局环境的测试根。 vm.release(globals).unwrap(); + // 归还旧对象最后的测试根,随后堆应为空。 vm.release(shared).unwrap(); assert_eq!(vm.object_count(), 0); } #[test] +/// 由 Rust 测试框架调用,交替推进两个托管协程;宿主插入的全局写入应保留,证明恢复从保存的指令位置继续。 fn managed_coroutines_interleave_without_replaying_writes() { use endor_core::CoroutineProgress; + // 隔离对象堆与执行状态,使最终零对象断言只覆盖当前场景。 let mut vm = Vm::new().unwrap(); + // 全局字典由测试持初始根,函数和协程图另有内部引用。 let globals = vm.new_dict().unwrap(); + // 为当前场景分配已验证代码,常量及名字表由代码对象持有。 let code = make_code( &mut vm, &[ @@ -1749,26 +1840,33 @@ fn managed_coroutines_interleave_without_replaying_writes() { ..CodeLayout::default() }, ); + // 函数持有代码和全局环境,后续协程或暂停帧再持有函数。 let function = vm .new_function("work", code, globals, vec![], vec![]) .unwrap(); + // 第一份协程有自己的指令位置和累计预算,尚未执行函数体。 let first = vm .new_coroutine(function, &[], ExecutionLimits::default()) .unwrap(); + // 同一函数的第二份协程拥有独立帧,但与第一份共享全局字典。 let second = vm .new_coroutine(function, &[], ExecutionLimits::default()) .unwrap(); 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 } )); + // 宿主在两次切片之间改写 x;恢复若重放前缀,会错误覆盖此值。 vm.dict_set(globals, text("x"), text("interleaved")) .unwrap(); assert!(matches!( + // 足够大的量子完成余下四条指令,累计六条而非从头执行。 vm.resume_coroutine(first, 16).unwrap(), CoroutineProgress::Complete { value: Value::Nil, @@ -1783,9 +1881,12 @@ fn managed_coroutines_interleave_without_replaying_writes() { vm.dict_get(globals, &text("y")).unwrap(), Some(text("second")) ); + // 取消尚未完成的第二份协程并释放其帧;再次取消应返回 false。 assert!(vm.cancel_coroutine(second).unwrap()); assert!(!vm.cancel_coroutine(second).unwrap()); + // 已完成协程不能再次恢复,避免重执行函数副作用。 assert!(vm.resume_coroutine(first, 1).is_err()); + // 逐个归还测试拥有根;协程帧结束或取消后的内部边也应已经清理。 for object in [first, second, function, code, globals] { vm.release(object).unwrap(); } @@ -1793,32 +1894,45 @@ fn managed_coroutines_interleave_without_replaying_writes() { } #[test] +/// 由 Rust 测试框架调用,构造协程、函数和环境的无外根环,验证未显式取消的停放帧也可随不可达环回收。 fn parked_coroutine_cycles_are_collectable_without_cancellation() { + // 隔离对象堆与执行状态,使最终零对象断言只覆盖当前场景。 let mut vm = Vm::new().unwrap(); + // 全局字典由测试持初始根,函数和协程图另有内部引用。 let globals = vm.new_dict().unwrap(); + // 函数只返回 Nil,不必执行也能验证停放协程的引用环。 let code = return_constant(&mut vm, Value::Nil); + // 函数持有代码和全局环境,后续协程或暂停帧再持有函数。 let function = vm .new_function("cycle", code, globals, vec![], vec![]) .unwrap(); + // 创建托管协程对象,停放状态保留帧、函数与环境图。 let coroutine = vm .new_coroutine(function, &[], ExecutionLimits::default()) .unwrap(); + // 环境反向引用协程,闭合环境→协程→函数→环境环。 vm.dict_set(globals, text("task"), Value::Object(coroutine)) .unwrap(); + // 逐个归还外部测试根;此时停放帧尚未取消,内部引用环留给循环收集器处理。 for object in [coroutine, function, code, globals] { vm.release(object).unwrap(); } + // 所有外部根已归还,循环收集必须识别并清理包含停放帧的不可达图。 assert!(vm.collect_cycles() > 0); assert_eq!(vm.object_count(), 0); } #[test] +/// 由 Rust 测试框架调用,验证托管协程跨恢复累计指令预算,达到硬上限后记录回溯并清理帧。 fn managed_coroutine_hard_budget_accumulates_and_error_releases_frames() { use endor_core::CoroutineProgress; + // 隔离对象堆与执行状态,使最终零对象断言只覆盖当前场景。 let mut vm = Vm::new().unwrap(); + // 全局字典由测试持初始根,函数和协程图另有内部引用。 let globals = vm.new_dict().unwrap(); // Reaching ReturnValue at the limit succeeds; keep a third instruction // beyond the hard limit to exercise failure before the next Pending yield. + // 直接构建三条指令:前两条消耗预算,Return 位于上限之后以强制错误路径。 let code = vm .new_code( EndorCode::from_instructions( @@ -1833,9 +1947,11 @@ fn managed_coroutine_hard_budget_accumulates_and_error_releases_frames() { .unwrap(), ) .unwrap(); + // 函数持有代码和全局环境,后续协程或暂停帧再持有函数。 let function = vm .new_function("limited", code, globals, vec![], vec![]) .unwrap(); + // 创建托管协程对象,停放状态保留帧、函数与环境图。 let coroutine = vm .new_coroutine( function, @@ -1844,20 +1960,26 @@ fn managed_coroutine_hard_budget_accumulates_and_error_releases_frames() { max_frames: 4, // The second resume reaches the cumulative limit before // the third instruction, without replaying the first. + // 硬上限跨量子累计;第二次恢复不能获得新的两条预算。 max_instructions: 2, }, ) .unwrap(); assert!(matches!( + // 首量子只执行 Nop 并停放,累计已用一条。 vm.resume_coroutine(coroutine, 1).unwrap(), CoroutineProgress::Pending { instructions: 1 } )); assert!(matches!( + // 第二次执行 LoadConst 后预算耗尽,尚未 Return,因此应终止而非再次 Pending。 vm.resume_coroutine(coroutine, 1), Err(VmError::ExecutionLimit("instructions")) )); + // 预算失败仍应留下当前调用的回溯,供宿主定位执行位置。 assert!(!vm.last_traceback().is_empty()); + // 错误路径已经清理帧,之后取消应报告没有可取消任务。 assert!(!vm.cancel_coroutine(coroutine).unwrap()); + // 逐个归还测试拥有根;协程帧结束或取消后的内部边也应已经清理。 for object in [coroutine, function, code, globals] { vm.release(object).unwrap(); } -- 2.52.0 From 2ca839c0d8e51655d867ab8a2b08fdef7cb8bc5e Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 18:19:51 +0800 Subject: [PATCH 37/62] docs(core): explain await ownership and precise read tests --- crates/core/tests/execution.rs | 67 ++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/crates/core/tests/execution.rs b/crates/core/tests/execution.rs index 3533ecb..4a790de 100644 --- a/crates/core/tests/execution.rs +++ b/crates/core/tests/execution.rs @@ -1986,46 +1986,66 @@ fn managed_coroutine_hard_budget_accumulates_and_error_releases_frames() { assert_eq!(vm.object_count(), 0); } +/// await 测试专用宿主载荷,由测试分配到 VM;eval 的 Await 指令通过 ObjectPayload 轮询它。 +/// 只切换宿主就绪标志,不改变托管引用边;结果对象始终由 trace 报告给 Heap。 #[derive(Debug)] struct AwaitGate { + // 测试与载荷共享就绪标志;测试 Release 写入,poll_await Acquire 读取。 ready: Arc, + // 就绪后返回的借用值;若为对象,分配时由 trace 建立内部拥有边。 value: Value, } +// 实现宿主对象协议,接入 Store 的种类检查、Heap 的引用遍历和 eval 的等待操作。 impl ObjectPayload for AwaitGate { + /// Store/宿主载荷检查调用,保留具体 Rust 类型以支持 Any 下转。 fn as_any(&self) -> &dyn Any { self } + /// 对象种类诊断调用,提供稳定的测试载荷名称。 fn kind_name(&self) -> &'static str { "await-gate" } + /// Heap 分配、回收时遍历内部对象边;立即值没有托管引用,无需报告。 fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { + // 只有对象值携带堆引用;必须报告这一边,使测试释放原根后结果仍存活。 if let Value::Object(reference) = self.value { visit(reference); } } + /// eval 每次进入 Await 时调用;非阻塞返回 Pending 或借用 Ready 值,不自行增加结果根。 fn poll_await(&self) -> Result { + // 与测试的 Release 写入配对;标志可变而 value 的托管引用关系保持不变。 Ok(if self.ready.load(Ordering::Acquire) { + // clone 只复制 Value 表示;eval 在丢弃等待操作数前为结果取得独立根。 endor_core::AwaitPoll::Ready(self.value.clone()) } else { + // 尚未就绪时帧保留原操作数与 Await 位置,下次只重新轮询该指令。 endor_core::AwaitPoll::Pending }) } } #[test] +/// 由 Rust 测试框架调用,验证 Await 停放不重放前缀,等待图保活结果,完成后结果根独立于协程存活。 fn await_keeps_operand_and_owned_object_result_without_replaying_prefix() { use endor_core::CoroutineProgress; + // 本测试独享堆和协程执行状态,最后检查结果归还后没有残留对象。 let mut vm = Vm::new().unwrap(); + // 宿主持一个共享标志,初始未就绪;停放后通过它唤醒下一次轮询。 let ready = Arc::new(std::sync::atomic::AtomicBool::new(false)); + // 分配最终结果并写入 marker,稍后释放原根以排除宿主根掩盖保活错误。 let value = vm.new_object().unwrap(); vm.set_attr(value, "marker", text("kept")).unwrap(); + // 分配等待载荷,Heap 根据 trace 保留 value;宿主标志使用 Arc 共享。 let gate = vm .allocate(AwaitGate { ready: ready.clone(), value: Value::Object(value), }) .unwrap(); + // 环境保存可观察的前缀副作用,等待后宿主将改写它以检测指令重放。 let globals = vm.new_dict().unwrap(); + // 先写 step,再载入 gate 并 Await,最后返回等待结果;两次恢复应只重试 Await。 let code = make_code( &mut vm, &[ @@ -2041,21 +2061,30 @@ fn await_keeps_operand_and_owned_object_result_without_replaying_prefix() { ..CodeLayout::default() }, ); + // 函数拥有代码与环境,代码常量表另持 gate。 let function = vm .new_function("awaited", code, globals, vec![], vec![]) .unwrap(); + // 创建可变托管协程;普通同步入口不支持停放的 Pending 等待。 let coroutine = vm .new_coroutine(function, &[], ExecutionLimits::default()) .unwrap(); + // 首轮应返回 Waiting,携带的借用等待对象必须正是 gate。 assert!( matches!(vm.resume_coroutine(coroutine, 64).unwrap(), CoroutineProgress::Waiting { awaitable, .. } if awaitable == gate) ); + // 归还结果原根,接下来靠 gate 的内部边保活。 vm.release(value).unwrap(); + // 归还等待对象原根,代码常量及停放帧操作数仍持有它。 vm.release(gate).unwrap(); + // 等待期间宿主改写前缀结果;恢复后必须保留该写入。 vm.dict_set(globals, text("step"), text("interleaved")) .unwrap(); + // 强制回收检查等待图仍可达,不能把帧上的 await 操作数误判为垃圾。 vm.collect_cycles(); + // 发布就绪状态;下一次恢复应从保存的 Await 指令重新轮询。 ready.store(true, Ordering::Release); + // 就绪后应取得拥有结果;不应再次返回 Waiting 或重跑整段代码。 let CoroutineProgress::Complete { value: result, .. } = vm.resume_coroutine(coroutine, 64).unwrap() else { @@ -2065,26 +2094,36 @@ fn await_keeps_operand_and_owned_object_result_without_replaying_prefix() { vm.dict_get(globals, &text("step")).unwrap(), Some(text("interleaved")) ); + // 清除协程、函数、代码与环境的测试根,验证结果根本身足以保持对象。 for object in [coroutine, function, code, globals] { vm.release(object).unwrap(); } + // 结果必须仍是有效对象,marker 检查不能依赖已释放的等待图。 let Value::Object(reference) = result else { panic!("owned object result") }; assert_eq!(vm.get_attr(reference, "marker").unwrap(), text("kept")); + // 最后归还调用结果拥有根,此时堆应为空。 vm.release_value(result).unwrap(); assert_eq!(vm.object_count(), 0); } #[test] +/// 由 Rust 测试框架调用,比较切片、同步精确及兼容读取集合;验证无关名称不使名称依赖失效,而遮蔽、命中值变化及对象销毁会失效。 fn named_reads_ignore_unrelated_globals_but_track_missing_shadow_and_payload_kind() { + // 隔离读取作用域与对象生命周期,后续销毁测试不会受其他用例影响。 let mut vm = Vm::new().unwrap(); + // 全局字典起初缺 name;这个缺失也是必须记录的观察。 let globals = vm.new_dict().unwrap(); + // 回退字典提供 name=builtin,全局缺失后才读取它。 let builtins = vm.new_dict().unwrap(); + // 初始化回退值,后续全局缺失时应读取这个内置名称。 vm.dict_set(builtins, text("name"), text("builtin")) .unwrap(); + // 在函数创建前绑定内置字典,函数保存该内置环境引用。 vm.dict_set(globals, text("__builtins__"), Value::Object(builtins)) .unwrap(); + // 只读取名称 name 后返回,避免其他内容读取把依赖扩大为整个字典版本。 let code = make_code( &mut vm, &[op(Opcode::LoadGlobal, 0), simple(Opcode::ReturnValue)], @@ -2094,9 +2133,11 @@ fn named_reads_ignore_unrelated_globals_but_track_missing_shadow_and_payload_kin ..CodeLayout::default() }, ); + // 构造具有独立代码/环境内部边的被测函数。 let function = vm .new_function("read-name", code, globals, vec![], vec![]) .unwrap(); + // 量子足够一次完成;分别取得拥有结果、兼容对象集合和精确依赖快照。 let endor_core::ExecutionProgress::Complete { value, reads, @@ -2109,38 +2150,48 @@ fn named_reads_ignore_unrelated_globals_but_track_missing_shadow_and_payload_kin panic!("complete") }; assert_eq!(value, text("builtin")); + // 全局缺失观察与内置命中观察都必须出现在兼容读取集合里。 assert!(reads.contains(&globals) && reads.contains(&builtins)); + // 归还本次调用结果;读取集合和依赖本身只是借用记录,不新增对象根。 vm.release_value(value).unwrap(); + // 增加无关名称,不能影响此前 name 缺失与内置命中的精确观察。 vm.dict_set(globals, text("_"), text("unrelated")).unwrap(); + // 同步精确入口应产生与切片入口相同的对象集合及依赖。 let (value, synchronous) = vm .call_read_only_precise_with_limits(function, &[], ExecutionLimits::default()) .unwrap(); assert_eq!(value, text("builtin")); assert_eq!(synchronous.dependencies, dependencies); assert_eq!(synchronous.objects, reads); + // 归还本次调用结果;读取集合和依赖本身只是借用记录,不新增对象根。 vm.release_value(value).unwrap(); + // 兼容入口仍返回对象集合,验证旧接口与精确接口的覆盖一致。 let (value, conservative) = vm .call_read_only_with_limits(function, &[], ExecutionLimits::default()) .unwrap(); assert_eq!(conservative, reads); + // 归还本次调用结果;读取集合和依赖本身只是借用记录,不新增对象根。 vm.release_value(value).unwrap(); assert!( dependencies .iter() .all(|dependency| vm.object_store().read_dependency_valid(dependency)) ); + // 新增同名全局遮蔽内置值,必须使原来的全局缺失依赖失效。 vm.dict_set(globals, text("name"), text("shadow")).unwrap(); assert!( dependencies .iter() .any(|dependency| !vm.object_store().read_dependency_valid(dependency)) ); + // 删除遮蔽后,名称观察重新与原快照一致;依赖不是永久失效标记。 vm.dict_delete(globals, &text("name")).unwrap(); assert!( dependencies .iter() .all(|dependency| vm.object_store().read_dependency_valid(dependency)) ); + // 实际命中值变化使内置名称快照失效。 vm.dict_set(builtins, text("name"), text("changed")) .unwrap(); assert!( @@ -2148,6 +2199,7 @@ fn named_reads_ignore_unrelated_globals_but_track_missing_shadow_and_payload_kin .iter() .any(|dependency| !vm.object_store().read_dependency_valid(dependency)) ); + // 归还全部测试根,环境和代码图释放后原依赖中的句柄不再有效。 for object in [function, code, globals, builtins] { vm.release(object).unwrap(); } @@ -2160,13 +2212,20 @@ fn named_reads_ignore_unrelated_globals_but_track_missing_shadow_and_payload_kin } #[test] +/// 由 Rust 测试框架调用,直接在 ObjectStore 检查子区间读取及重验均登记到外层;按名称依赖保持精确,整字典读取仍保守失效。 fn precise_nested_regions_replay_borrowed_dependencies_without_hiding_full_dict_reads() { + // 直接使用 ObjectStore,只验证读取追踪,无需创建可执行函数或运行解释器。 let mut vm = endor_core::ObjectStore::new().unwrap(); + // 准备只含 name 的字典,之后添加其他名称来区分精确与整对象观察。 let globals = vm.new_dict().unwrap(); vm.dict_set(globals, text("name"), text("value")).unwrap(); + // 开启只读外层作用域,收集兼容对象集合及精确观察,退出后才允许写入旧对象。 let scope = vm.begin_read_only(); + // 在现有只读作用域内标记子区间,结束子区间不会清除外层已登记的依赖。 let token = vm.begin_read_tracking().unwrap(); + // 记录字典种类与指定名称命中值,不记录无关名称或整个内容版本。 vm.dict_get_name(globals, "name").unwrap(); + // 结束子区间,取得借用的访问分类与依赖;这些记录不会自动持有对象根。 let tracked = vm.end_read_tracking_precise(token).unwrap(); assert!( tracked @@ -2174,10 +2233,13 @@ fn precise_nested_regions_replay_borrowed_dependencies_without_hiding_full_dict_ .iter() .any(|access| access.object == globals) ); + // 在外层作用域仍开启时重验子区间依赖;正常读取入口会再次登记观察。 for dependency in &tracked.dependencies { assert!(vm.read_dependency_valid(dependency)); } + // 结束外层只读作用域取得累计观察,再进行写入以检验失效范围。 let reads = vm.end_read_only_precise(scope).unwrap(); + // 首次只读仅观察 name,新增 other 后所有原依赖应仍然有效。 vm.dict_set(globals, text("other"), Value::Nil).unwrap(); assert!( reads @@ -2185,9 +2247,13 @@ fn precise_nested_regions_replay_borrowed_dependencies_without_hiding_full_dict_ .iter() .all(|dependency| vm.read_dependency_valid(dependency)) ); + // 开启只读外层作用域,收集兼容对象集合及精确观察,退出后才允许写入旧对象。 let scope = vm.begin_read_only(); + // 第二轮读取全部条目,必须记录整字典版本,不能被之前的精确读取策略掩盖。 vm.dict_items(globals).unwrap(); + // 结束外层只读作用域取得累计观察,再进行写入以检验失效范围。 let reads = vm.end_read_only_precise(scope).unwrap(); + // 整字典内容变化后,应至少有一项整对象依赖失效。 vm.dict_set(globals, text("third"), Value::Nil).unwrap(); assert!( reads @@ -2195,6 +2261,7 @@ fn precise_nested_regions_replay_borrowed_dependencies_without_hiding_full_dict_ .iter() .any(|dependency| !vm.read_dependency_valid(dependency)) ); + // 归还字典唯一外部根;读取记录不拥有根,因此堆应清空。 vm.release(globals).unwrap(); assert_eq!(vm.object_count(), 0); } -- 2.52.0 From 8937b8f8d2e89b96a1e89bcb7e3bf3b7faa773c9 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 18:21:28 +0800 Subject: [PATCH 38/62] docs(compiler): explain compilation entry points and source examples --- crates/compiler/examples/calls.endor | 8 ++++++++ crates/compiler/examples/hello.endor | 3 ++- crates/compiler/examples/literal.endor | 2 +- crates/compiler/examples/number.endor | 4 ++++ crates/compiler/examples/shadowing.endor | 8 ++++++++ crates/compiler/src/lib.rs | 22 ++++++++++++++++++++++ 6 files changed, 45 insertions(+), 2 deletions(-) diff --git a/crates/compiler/examples/calls.endor b/crates/compiler/examples/calls.endor index 4d5744c..90fdd0f 100644 --- a/crates/compiler/examples/calls.endor +++ b/crates/compiler/examples/calls.endor @@ -1,13 +1,21 @@ +// compiler/tests/pipeline.rs 将此文件作为模块编译,加载后由测试调用 main,检查前向全局函数查找。 // The callee is declared after main. Parameters, locals and calls use wordcode. +// 模块入口由宿主/测试在模块执行完成后调用;先 echo,再把局部结果交给 relay。 function main(): String { + // 直接调用后面声明的 echo,返回值写入当前帧的 message 局部槽。 let message = echo("函数调用已接通") + // 将局部值作为实参传给 relay,返回其结果给宿主。 return relay(message) } +// main 调用的中转函数;value 是当前帧参数槽,继续调用全局 echo。 function relay(value: String): String { + // 普通函数调用逐层返回相同字符串,验证嵌套调用的参数传递与返回。 return echo(value) } +// main 和 relay 共用的恒等函数,参数值直接成为调用结果。 function echo(value: String): String { + // 无额外转换,直接把参数值交给调用者。 return value } diff --git a/crates/compiler/examples/hello.endor b/crates/compiler/examples/hello.endor index 74f4030..2cfbf7e 100644 --- a/crates/compiler/examples/hello.endor +++ b/crates/compiler/examples/hello.endor @@ -1,4 +1,5 @@ -// cli run crates/compiler/examples/hello.endor → 你好,Endor 🌱 +// compiler/tests/pipeline.rs 编译并加载本模块后调用 main,期望返回“你好,Endor 🌱”。 function main(): String { + // 返回 Unicode 文本字面量,测试核对编译及 VM 返回过程保留原文。 return "你好,Endor 🌱" } diff --git a/crates/compiler/examples/literal.endor b/crates/compiler/examples/literal.endor index d60820f..fdbd795 100644 --- a/crates/compiler/examples/literal.endor +++ b/crates/compiler/examples/literal.endor @@ -1,2 +1,2 @@ -// cli eval crates/compiler/examples/literal.endor +// compiler/tests/pipeline.rs 以 Expression 模式编译此文件,VM 执行后直接取得文本;不定义 main。 "表达式 → 字节码 → VM" diff --git a/crates/compiler/examples/number.endor b/crates/compiler/examples/number.endor index 3da4a47..89c9073 100644 --- a/crates/compiler/examples/number.endor +++ b/crates/compiler/examples/number.endor @@ -1,5 +1,9 @@ +// compiler/tests/pipeline.rs 按 Module 模式编译,执行模块后调用 main 并按精确十进制值比较。 // Signed decimal constants retain all digits; no f64 conversion or arithmetic. +// 由测试/宿主调用,验证长十进制带符号常量经过编译与返回仍不丢精度。 function main(): Number { + // 字面量保存所有十进制数字,不先经 f64;源码负号经 signed_literal 合入精确数值拼写。 let value = -123456789012345678901234567890.00100 + // 返回局部槽中的数值,用于检查整个管线保持原来的精确值。 return value } diff --git a/crates/compiler/examples/shadowing.endor b/crates/compiler/examples/shadowing.endor index e509fdb..16578b0 100644 --- a/crates/compiler/examples/shadowing.endor +++ b/crates/compiler/examples/shadowing.endor @@ -1,13 +1,21 @@ +// compiler/tests/pipeline.rs 加载模块并调用 main,检查函数值传参及局部名优先于全局声明。 // A parameter can shadow a module function, and functions are ordinary values. +// main 把此全局函数作为普通值传入 invoke;实际调用时返回收到的参数。 function echo(value: String): String { + // 恒等返回,不改变用于确认调用链的文本。 return value } +// main 调用;参数 echo 遮蔽同名模块函数,value 保存待转发的字符串。 function invoke(echo: Callable, value: String): String { + // 从局部参数 echo 复制函数值,而非再次查找模块的同名全局。 let chosen = echo + // 经局部 chosen 间接调用函数,把 value 作为实参并返回调用结果。 return chosen(value) } +// 宿主/测试入口,先取得全局 echo 函数值,再传给 invoke 的同名参数。 function main(): String { + // invoke 内部用局部参数解析 echo,最终应原样返回这段文本。 return invoke(echo, "局部名称优先") } diff --git a/crates/compiler/src/lib.rs b/crates/compiler/src/lib.rs index bdaec01..4b5f72d 100644 --- a/crates/compiler/src/lib.rs +++ b/crates/compiler/src/lib.rs @@ -1,31 +1,46 @@ +//! 宿主与集成测试使用的源码编译门面:输出不依赖 VM 堆的模板,显式加载后才得到代码对象。 //! Minimal source-to-wordcode compiler. Analysis and emission do not allocate //! VM objects; a compiled unit can be loaded into independent VMs explicitly. +// 名称分析决定局部槽与全局名字表,供下一个字节码生成阶段使用。 mod analysis; +// 生成可跨 VM 复用的代码模板;仅显式 load 时才分配目标 VM 的对象。 mod codegen; +// 对宿主和测试公开名称解析结果,便于检查绑定与源码位置。 pub use analysis::{NameBinding, NameUse, Scope, ScopeKind, SymbolTable, analyze}; +// 宿主取得模板后负责选择 VM 并调用 CompiledCode::load。 pub use codegen::CompiledCode; use frontend::ast::{AstDocument, AstRoot}; use frontend::{Diagnostic, DiagnosticKind, ParseMode, SourceDiagnostic, SourceUnit, Span}; +/// 宿主通过 compile_source/compile_ast 提供的编译选项;名称分析据此允许访问宿主全局或内置名。 +/// 这里只声明名称,宿主仍需在执行前向 VM 的环境字典提供实际值。 /// Names supplied by the host's globals or builtins dictionary. These are /// compile-time declarations only; loading code never injects their values. #[derive(Debug, Clone, Default)] pub struct CompileOptions { + // 编译期已知名称列表;analyze 与源码声明共同解析可用的全局名,不在此持有 VM 值。 pub globals: Vec, } +/// 宿主及 compiler 集成测试的源码编译入口:按指定模式解析,再校验 AST、解析名称并生成模板。 +/// source 提供文件身份与原始文本;mode 决定模块、表达式或交互根;options 声明额外宿主名称。 +/// 解析错误已有源码诊断;后续编译错误在这里附加同一源码的位置和片段,成功不执行任何 VM 指令。 pub fn compile_source( source: &SourceUnit, mode: ParseMode, options: &CompileOptions, ) -> Result { + // 前端根据模式返回对应 AstRoot,解析失败立即把带源码信息的错误传给宿主。 let root = frontend::parse_source_ast(source, mode)?; + // AST 入口返回不含源码快照的 Diagnostic;在此统一补上 SourceUnit 上下文。 compile_ast(&root, source, options).map_err(|error| source.annotate(error)) } +/// compile_source 及已有 AST 的宿主/测试调用;先验证结构与跨度,再分析名称,最后输出代码模板。 +/// 传入 source 必须是期望匹配的位置快照;跨度有效只说明位置在范围内,不能证明 AST 与文本语义相同。 /// Compile an AST against its original source snapshot. Span validation does /// not prove the AST's semantic equivalence to the supplied text. pub fn compile_ast( @@ -33,8 +48,10 @@ pub fn compile_ast( source: &SourceUnit, options: &CompileOptions, ) -> Result { + // 包装并校验 AST 文档结构,再检查节点跨度对当前 source 文本是否有效;不修改调用方的树。 AstDocument::new(root.clone()) .and_then(|document| document.validate_source(source.text())) + // 文档结构/跨度验证错误归入编译错误,尚无可靠单一节点位置,使用默认跨度。 .map_err(|error| { diagnostic( DiagnosticKind::Compilation, @@ -42,11 +59,16 @@ pub fn compile_ast( error.to_string(), ) })?; + // 收集并解析所有作用域的绑定及代码布局;错误会阻止生成阶段继续。 let symbols = analyze(root, options)?; + // 生成器按解析后的槽/名字索引编码,不在此加载模板或分配 VM 堆对象。 codegen::generate(root, source, &symbols) } +/// 本模块的 AST 验证及 analysis/codegen 的错误辅助函数共用,统一构造不带源码快照的诊断。 +/// kind 区分语义/编译错误,span 标记位置,message 接受可转 String 的说明;语法期望项留空。 fn diagnostic(kind: DiagnosticKind, span: Span, message: impl Into) -> Diagnostic { + // 编译器错误没有解析器的“期望 token”列表,源码上下文由上层 compile_source 统一附加。 Diagnostic { kind, span, -- 2.52.0 From 6792ef9a9934ee1a1e1ef85156a86405f2f666a6 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 18:28:02 +0800 Subject: [PATCH 39/62] docs(compiler): explain scope analysis and wordcode generation --- crates/compiler/src/analysis.rs | 125 ++++++++++++++++++++++++++++++++ crates/compiler/src/codegen.rs | 119 ++++++++++++++++++++++++++++++ 2 files changed, 244 insertions(+) diff --git a/crates/compiler/src/analysis.rs b/crates/compiler/src/analysis.rs index 9dea769..1c01e32 100644 --- a/crates/compiler/src/analysis.rs +++ b/crates/compiler/src/analysis.rs @@ -1,3 +1,5 @@ +//! compile_ast 调用的名称与确定初始化分析;只构造 Scope/CodeLayout,不分配 VM 对象。 +//! 先登记所有顶层声明,再分析函数体;codegen 根据名称、跨度和绑定索引选择 LoadFast/LoadGlobal。 use std::collections::BTreeSet; use endor_core::CodeLayout; @@ -7,48 +9,72 @@ use frontend::{Diagnostic, DiagnosticKind, Span}; use crate::{CompileOptions, diagnostic}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// 根节点或函数的编译上下文;生成器据此区分局部槽写入与全局环境写入。 pub enum ScopeKind { + // 模块声明入口,执行后通常返回 Nil。 Module, + // 单个表达式入口,直接返回其求值结果。 Expression, + // 单条交互输入,可声明、赋值或返回表达式结果。 Interactive, + // 函数或类方法体,拥有参数、局部槽和 return 语义。 Function, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// expression 解析的读取位置;codegen 使用此索引编码对应的加载指令。 pub enum NameBinding { + // CodeLayout.locals 中的位置,参数也占局部槽。 Local(usize), + // CodeLayout.names 中的位置,运行时先查 globals 再查捕获的 builtins。 Global(usize), } #[derive(Debug, Clone, PartialEq, Eq)] +/// 一次名称表达式的解析结果;名字加源码跨度定位具体使用点,避免同名出现互相混淆。 pub struct NameUse { + // 名称文本;Scope 使用它作为代码名,NameUse 使用它标识被读取的名称。 pub name: String, + // 原源码中的半开字节范围,用于诊断及生成阶段定位。 pub span: Span, + // 名称读取已解析到局部槽或全局名字索引。 pub binding: NameBinding, } #[derive(Debug, Clone)] +/// 一个代码单元的布局与读取记录,由 analyze/function_scope 构造,Emitter 借用。 pub struct Scope { + // 决定赋值存局部还是全局,以及是否允许 return。 pub kind: ScopeKind, + // 名称文本;Scope 使用它作为代码名,NameUse 使用它标识被读取的名称。 pub name: String, + // 原源码中的半开字节范围,用于诊断及生成阶段定位。 pub span: Span, + // VM 代码验证及帧分配需要的参数、局部名和名字表。 pub layout: CodeLayout, + // 每次名称表达式的绑定,按分析访问顺序追加,供生成器按跨度检索。 pub references: Vec, } #[derive(Debug, Clone)] +/// compile_ast 把整个解析结果交给 codegen;模板生成不再自行猜测名称绑定。 pub struct SymbolTable { + // 根代码单元,即模块、表达式或交互输入的作用域。 pub root: Scope, /// Function and class-method scopes in declaration order. Nested declarations /// are rejected until source closure semantics are implemented. + // 顶层函数和类方法按声明顺序展平;生成器以名称和跨度查找对应体。 pub functions: Vec, } +/// 本文件所有拒绝路径共用,把具体节点跨度与说明包装为 Semantic 诊断。 fn error(span: Span, message: impl Into) -> Diagnostic { diagnostic(DiagnosticKind::Semantic, span, message) } +/// analyze 登记顶层声明时调用,统一取得函数或类的名字节点(含诊断位置)。 fn declaration_name(declaration: &Decl) -> &frontend::ast::Name { + // 按声明种类取相同用途的名字,不把类方法当作顶层全局声明。 match &declaration.kind { DeclKind::Function(function) => &function.name, DeclKind::Class(class) => &class.name, @@ -58,7 +84,10 @@ fn declaration_name(declaration: &Decl) -> &frontend::ast::Name { /// Resolve a closed source unit plus explicitly declared host globals. /// Function locals are collected before reading expressions; a later let /// shadows the global for the entire function, but cannot be read early. +/// compile_ast 和直接分析测试调用;options 仅声明额外全局名,返回根及各函数的绑定布局。 +/// 函数体先收集所有局部名,因此后面的 let 也会遮蔽前面的全局读取,但未初始化读取会报错。 pub fn analyze(root: &AstRoot, options: &CompileOptions) -> Result { + // 交互模式要求恰好一条语句;空输入或批量输入不是本编译入口支持的单位。 if let AstRoot::Interactive(input) = root && input.statements.len() != 1 { @@ -67,15 +96,19 @@ pub fn analyze(root: &AstRoot, options: &CompileOptions) -> Result = options.globals.iter().cloned().collect(); + // 在分析任何代码前拒绝空宿主名,避免不可解析的名称混入绑定集合。 if globals.contains("") { return Err(error(root.span(), "host global names must not be empty")); } + // 提取根级函数/类声明,表达式根没有声明;交互输入只取声明语句。 let declarations: Vec<&Decl> = match root { AstRoot::Module(program) => program.declarations.iter().collect(), AstRoot::Interactive(input) => input .statements .iter() + // 交互输入仅声明语句进入预登记列表,其余语句稍后按执行语义分析。 .filter_map(|statement| match &statement.kind { StmtKind::Declaration(declaration) => Some(declaration), _ => None, @@ -83,32 +116,43 @@ pub fn analyze(root: &AstRoot, options: &CompileOptions) -> Result Vec::new(), }; + // 展平顶层函数及类方法,保留原声明顺序供统一分析。 let functions: Vec<&Function> = declarations .iter() + // 类本身不生成函数体作用域,其 methods 逐个参与分析。 .flat_map(|declaration| match &declaration.kind { DeclKind::Function(function) => vec![function], DeclKind::Class(class) => class.methods.iter().collect(), }) .collect(); + // 按根类型选作用域种类及用于代码诊断的合成名字。 let (kind, name) = match root { AstRoot::Module(_) => (ScopeKind::Module, ""), AstRoot::Expression(_) => (ScopeKind::Expression, ""), AstRoot::Interactive(_) => (ScopeKind::Interactive, ""), }; + // 根作用域记录声明、属性等名字表及根表达式读取,生成器随后借用它。 let mut root_scope = Builder::new(kind, name, root.span()); + // 单独检查本源码顶层重名;宿主同名声明不在这个集合中。 let mut declared = BTreeSet::new(); + // 遍历根级声明;首轮注册名称,次轮检查类结构,保证函数体分析时名字已齐全。 for declaration in &declarations { + // 取得声明的名字和精确跨度,重名错误定位到名字本身。 let name = declaration_name(declaration); declare(&mut declared, &name.text, name.span)?; globals.insert(name.text.clone()); root_scope.intern(&name.text); } + // 遍历根级声明;首轮注册名称,次轮检查类结构,保证函数体分析时名字已齐全。 for declaration in &declarations { + // 类声明额外检查基类名和成员结构,普通函数跳过这一段。 if let DeclKind::Class(class) = &declaration.kind { + // 未指定基类时默认 Object;这里只确认名称合法,实际 Type 在执行时解析。 let base_name = class .base .as_ref() .map_or("Object", |base| base.text.as_str()); + // 基类必须是宿主或源码已声明的名称,不隐式引入未知全局。 if !globals.contains(base_name) { return Err(error( class.span, @@ -116,12 +160,16 @@ pub fn analyze(root: &AstRoot, options: &CompileOptions) -> Result Result, _>>()?; + // 声明预处理后,按根类型分析真正需要求值的内容。 match root { + // 模块根只含已处理的声明,没有额外顶层语句需要分析。 AstRoot::Module(_) => {} AstRoot::Expression(expression) => root_scope.expression(expression, &globals)?, + // 交互根按唯一语句的种类决定名称及赋值规则。 AstRoot::Interactive(input) => { + // analyze 开头已保证只有一条,仍按列表结构遍历 AST。 for statement in &input.statements { + // 区分绑定、赋值、成员更新与控制流,不能把 return 当成交互结果。 match &statement.kind { + // 新绑定先检查重名并分析右值,完成后才把名称加入可用全局集合。 StmtKind::Binding(binding) => { declare(&mut declared, &binding.name.text, binding.name.span)?; root_scope.expression(&binding.value, &globals)?; globals.insert(binding.name.text.clone()); root_scope.intern(&binding.name.text); } + // 交互赋值先解析右值,再登记写入目标;目标无需预先存在。 StmtKind::Assignment(assignment) => { root_scope.expression(&assignment.value, &globals)?; globals.insert(assignment.name.text.clone()); root_scope.intern(&assignment.name.text); } + // 成员写入分析接收对象与右值,并把属性名加入名字表。 StmtKind::MemberAssignment(assignment) => { root_scope.expression(&assignment.object, &globals)?; root_scope.expression(&assignment.value, &globals)?; root_scope.intern(&assignment.name.text); } + // 声明已经预登记且函数体已统一分析,不能重复分析。 StmtKind::Declaration(_) => {} + // 交互表达式只需验证读取,不创建新绑定。 StmtKind::Expression(expression) => { root_scope.expression(expression, &globals)? } + // 复合语句先收集块内名称到根名字表,再复用语句控制流分析。 StmtKind::If(_) | StmtKind::While(_) => { + // 借一个节点组成切片,复用预收集和控制流分析,不克隆 AST。 let statements = std::slice::from_ref(statement); collect_locals( statements, @@ -177,6 +238,7 @@ pub fn analyze(root: &AstRoot, options: &CompileOptions) -> Result { return Err(error(statement.span, "return outside a function")); } @@ -190,7 +252,9 @@ pub fn analyze(root: &AstRoot, options: &CompileOptions) -> Result, name: &str, span: Span) -> Result<(), Diagnostic> { + // 空名或重复插入失败立即报错;成功 insert 同时完成名称登记。 if name.is_empty() || !names.insert(name.to_owned()) { return Err(error( span, @@ -200,9 +264,13 @@ fn declare(names: &mut BTreeSet, name: &str, span: Span) -> Result<(), D Ok(()) } +/// analyze 为每个顶层函数/类方法调用;参数先占局部槽,再收集全体局部名并按控制流检查初始化。 fn function_scope(function: &Function, globals: &BTreeSet) -> Result { + // 新建函数/方法作用域,局部布局及初始化状态彼此独立。 let mut builder = Builder::new(ScopeKind::Function, &function.name.text, function.span); + // 跟踪参数和局部名是否重复,与当前路径是否初始化分开记录。 let mut locals = BTreeSet::new(); + // 参数在函数进入时已有值,按声明顺序同时登记参数表和前部局部槽。 for parameter in &function.parameters { declare(&mut locals, ¶meter.name.text, parameter.name.span)?; builder @@ -226,47 +294,63 @@ fn function_scope(function: &Function, globals: &BTreeSet) -> Result, layout: &mut Vec, ) -> Result<(), Diagnostic> { + // 依源码顺序遍历当前语句序列,递归进入块但不创建块级局部作用域。 for statement in statements { + // 预收集阶段仅关心能引入名称的语句或嵌套声明拒绝规则。 match &statement.kind { + // let 必须是首次声明,即使出现在不同分支也不能重复同一函数级名。 StmtKind::Binding(b) => { declare(locals, &b.name.text, b.name.span)?; layout.push(b.name.text.clone()); } + // 赋值允许写已有名;仅第一次出现时追加一个布局槽。 StmtKind::Assignment(a) => { + // 首次出现的赋值目标才分配槽;后续普通赋值复用原槽。 if locals.insert(a.name.text.clone()) { layout.push(a.name.text.clone()); } } + // 两侧的名称都预分配;这里不决定分支之后是否能读取它们。 StmtKind::If(b) => { collect_locals(&b.body.statements, locals, layout)?; + // 预收集 else 中的局部名,与 then 和外围共享同一函数级布局。 if let Some(other) = &b.otherwise { collect_locals(&other.statements, locals, layout)?; } } + // 循环体也预分配函数级名字,但执行零次的情况由后续初始化分析处理。 StmtKind::While(b) => collect_locals(&b.body.statements, locals, layout)?, + // 源码闭包/嵌套类尚未支持,不生成看似合法但缺捕获语义的布局。 StmtKind::Declaration(_) => { return Err(error( statement.span, "nested declarations are not compiled yet", )); } + // 不引入名字的表达式、返回和成员写入留给后续语句分析。 _ => {} } } Ok(()) } +/// 分析单个代码单元的可变状态;Scope 是输出,initialized 是当前可达路径上确定初始化的名称。 struct Builder { + // 正在构造的作用域,最终移交 SymbolTable。 scope: Scope, + // 仅控制流中保证赋值的局部名;分支取交集,循环出口恢复进入前集合。 initialized: BTreeSet, } impl Builder { + /// analyze/function_scope 创建空分析器;源码身份和布局初始值在这里统一设置。 fn new(kind: ScopeKind, name: &str, span: Span) -> Self { Self { scope: Scope { @@ -280,7 +364,9 @@ impl Builder { } } + /// 分析名称/属性/声明时调用;复用或追加名字表项,保证生成器查索引时必能找到。 fn intern(&mut self, name: &str) -> usize { + // 按文本查找已登记名字;命中时保留稳定索引。 if let Some(index) = self .scope .layout @@ -290,6 +376,7 @@ impl Builder { { index } else { + // 没有现有名字索引才追加,保留此前已记录的索引。 self.scope.layout.names.push(name.to_owned()); self.scope.layout.names.len() - 1 } @@ -297,75 +384,100 @@ impl Builder { /// Function-scoped locals; branches merge definite initialization. A loop /// may execute zero times, so its writes do not initialize later reads. + /// 函数体与复合语句递归调用;返回本语句序列是否所有可达路径都已 return。 fn statements( &mut self, statements: &[frontend::ast::Stmt], globals: &BTreeSet, ) -> Result { + // 表示当前语句序列是否所有可达路径都已返回,供后继不可达检查使用。 let mut returned = false; + // 按源码顺序推进当前可达路径的初始化状态,检查每条后继语句。 for statement in statements { + // 前面的 return 或双侧必返分支已终止所有路径,后继语句报不可达。 if returned { return Err(error( statement.span, "statement after return is unreachable", )); } + // 按控制流检查实际读取与初始化,不重新分配预收集过的局部槽。 match &statement.kind { + // 先分析右值,之后才把本局部标记已初始化,防止 let x = x 读取自身未绑定槽。 StmtKind::Binding(b) => { self.expression(&b.value, globals)?; self.initialized.insert(b.name.text.clone()); + // 根作用域写入全局而非局部槽,需要为 StoreGlobal 登记名字。 if self.scope.kind != ScopeKind::Function { self.intern(&b.name.text); } } + // 普通赋值同样先检查右值,再令目标在当前路径上已初始化。 StmtKind::Assignment(a) => { self.expression(&a.value, globals)?; self.initialized.insert(a.name.text.clone()); + // 根作用域写入全局而非局部槽,需要为 StoreGlobal 登记名字。 if self.scope.kind != ScopeKind::Function { self.intern(&a.name.text); } } + // 检查对象和右值读取,并为后续 StoreAttr 登记属性名字。 StmtKind::MemberAssignment(a) => { self.expression(&a.object, globals)?; self.expression(&a.value, globals)?; self.intern(&a.name.text); } StmtKind::Expression(e) => self.expression(e, globals)?, + // 只有函数可返回;可选返回表达式先接受同样的名称检查。 StmtKind::Return(r) => { + // 只有函数有 return 语义,根控制流块也不能绕过此限制。 if self.scope.kind != ScopeKind::Function { return Err(error(statement.span, "return outside a function")); } + // 无返回表达式由生成器补 Nil;有表达式时先检查其中所有名称读取。 if let Some(e) = &r.value { self.expression(e, globals)?; } returned = true; } + // 先分析条件,再以相同入口初始化集合分别分析两侧。 StmtKind::If(b) => { self.expression(&b.condition, globals)?; + // 保存控制流入口的确定初始化集合,另一分支及循环出口需要恢复它。 let before = self.initialized.clone(); + // 分析 then 分支并取得其是否必定返回的标志。 let yes_return = self.statements(&b.body.statements, globals)?; + // 保存 then 出口状态,恢复入口后再单独分析 else。 let yes = self.initialized.clone(); self.initialized = before; + // 有 else 时分析其状态;无 else 视为存在不返回的空路径。 let no_return = if let Some(other) = &b.otherwise { self.statements(&other.statements, globals)? } else { false }; + // then 已返回则只保留 else 的继续路径;否则按下方规则合并。 self.initialized = if yes_return { self.initialized.clone() } else if no_return { + // 只有 else 返回时保留 then 的继续路径。 yes } else { + // 两边都能继续时,只保留双方都保证初始化的名称。 yes.intersection(&self.initialized).cloned().collect() }; + // 只有两侧都必返时整个 if 才让后继语句不可达。 returned = yes_return && no_return; } + // 检查条件及循环体,但循环可能零次执行,出口恢复入口初始化状态。 StmtKind::While(b) => { self.expression(&b.condition, globals)?; + // 保存控制流入口的确定初始化集合,另一分支及循环出口需要恢复它。 let before = self.initialized.clone(); self.statements(&b.body.statements, globals)?; self.initialized = before; } + // 函数体/复合语句不支持嵌套声明,不能在这里隐式生成闭包作用域。 StmtKind::Declaration(_) => { return Err(error( statement.span, @@ -377,13 +489,17 @@ impl Builder { Ok(returned) } + /// analyze/statements 及自身递归调用;检查名称已声明/已初始化,记录读取绑定并登记属性名字。 fn expression( &mut self, expression: &Expr, globals: &BTreeSet, ) -> Result<(), Diagnostic> { + // 递归覆盖名称读取和子表达式;字面量不需要绑定。 match &expression.kind { + // 局部名优先于全局,即便这个局部尚未初始化也不回退到同名全局。 ExprKind::Name(name) => { + // 先查完整局部布局,未命中才考虑合法全局名,不按声明位置临时改变遮蔽规则。 let binding = if let Some(index) = self .scope .layout @@ -391,6 +507,7 @@ impl Builder { .iter() .position(|local| local == name) { + // 槽已预分配不代表当前路径已经赋值,未确定初始化必须拒绝读取。 if !self.initialized.contains(name) { return Err(error( expression.span, @@ -399,33 +516,41 @@ impl Builder { } NameBinding::Local(index) } else if globals.contains(name) { + // 没有同名局部时,宿主或源码登记过的全局名才能读取。 NameBinding::Global(self.intern(name)) } else { return Err(error(expression.span, format!("undefined name: {name}"))); }; + // 为此名字及源码位置保存绑定,codegen 据此选择 LoadFast 或 LoadGlobal。 self.scope.references.push(NameUse { name: name.clone(), span: expression.span, binding, }); } + // 一元及 await 都只分析操作数;是否允许实际等待由执行器处理。 ExprKind::Unary { operand, .. } | ExprKind::Await(operand) => { self.expression(operand, globals)? } + // 按左右顺序分析所有读取,类型及运算协议由运行时处理。 ExprKind::Binary { left, right, .. } => { self.expression(left, globals)?; self.expression(right, globals)?; } + // 对象表达式接受名称检查,属性本身仅登记为名字表项。 ExprKind::Member { object, name } => { self.expression(object, globals)?; self.intern(&name.text); } + // 调用目标和每个实参都可能读取名称,逐个递归检查。 ExprKind::Call { callee, arguments } => { self.expression(callee, globals)?; + // 按源码顺序检查参数,错误带各自表达式的位置。 for argument in arguments { self.expression(argument, globals)?; } } + // 字面量无变量读取;Symbol 是否可生成运行时值由 codegen 再拒绝。 ExprKind::Number(_) | ExprKind::String(_) | ExprKind::Symbol(_) => {} } Ok(()) diff --git a/crates/compiler/src/codegen.rs b/crates/compiler/src/codegen.rs index 215c762..50b4789 100644 --- a/crates/compiler/src/codegen.rs +++ b/crates/compiler/src/codegen.rs @@ -1,3 +1,5 @@ +//! compile_ast 的字节码生成阶段:消费 analysis 的稳定布局,先产生逻辑指令,再解析物理跳转距离。 +//! 编译产物没有 VM 对象引用;load 才递归分配子代码并替换常量占位,交由宿主执行。 use endor_core::{ CodeLayout, DecodedInstruction, EndorCode, EndorNumber, Instruction, ObjectRef, Opcode, Value, Vm, VmError, @@ -10,30 +12,40 @@ use crate::{NameBinding, Scope, ScopeKind, SymbolTable, diagnostic}; /// Validated, heap-independent executable template. Nested code constants /// remain templates until load(), so no ObjectRef is shared across VM heaps. #[derive(Debug, Clone)] +/// 宿主和编译集成测试取得的已验证模板,可多次加载到不同 VM,不跨堆共享 ObjectRef。 pub struct CompiledCode { + // 当前代码单元,嵌套代码位置先放 Nil;load 时构造目标 VM 专属的常量表。 code: EndorCode, + // (父常量索引, 子模板);Emitter 记录待替换位置,CompiledCode::load 递归装入。 children: Vec<(usize, CompiledCode)>, } impl CompiledCode { + /// 宿主/测试检查已解码指令及偏移,借用模板,不执行代码。 pub fn instructions(&self) -> &[DecodedInstruction] { self.code.instructions() } + /// 宿主/测试检查参数、局部与名字布局,与实际装载代码保持一致。 pub fn layout(&self) -> &CodeLayout { self.code.layout() } + /// 宿主/测试取得分析作用域的代码名,用于显示或诊断。 pub fn name(&self) -> &str { self.code.name() } + /// 宿主/测试检查函数源码类型注解;根模板通常没有函数签名。 pub fn source_signature(&self) -> Option<&endor_core::SourceSignature> { self.code.source_signature() } + /// 宿主/测试查看来自 SourceUnit 的来源标识,装载后用于回溯定位。 pub fn filename(&self) -> &str { self.code.filename() } + /// 宿主/测试查看作用域起点对应的源码行,缺少有效位置时为一。 pub fn first_line(&self) -> usize { self.code.first_line() } + /// 宿主/测试按登记顺序遍历直接子模板,不暴露内部常量占位索引。 pub fn children(&self) -> impl Iterator { self.children.iter().map(|(_, child)| child) } @@ -41,15 +53,23 @@ impl CompiledCode { /// Return one owned code-object root; release it after execution. The /// parent retains its nested code constants. Temporary child roots are /// released on both success and failure, including partial recursive loads. + /// 宿主/测试显式装入目标 VM;成功返回一个拥有代码根,调用者执行完成后负责 release。 + /// 已分配子根无论后续成功或失败都在外层归还,父代码成功分配时已有自己的常量内部边。 pub fn load(&self, vm: &mut Vm) -> Result { + // 记录本次已成功加载的子代码拥有根,外层统一归还,覆盖递归中途失败。 let mut roots = Vec::new(); + // 把可失败步骤放进闭包,使 ? 只退出该阶段,随后仍执行根清理。 let result = (|| { + // 复制模板常量,不修改共享模板本身;子代码位置稍后替换为目标堆句柄。 let mut constants = self.code.constants().to_vec(); + // 递归装入每个子模板,失败时先前成功得到的根仍在 roots 中可清理。 for (index, child) in &self.children { + // 子装载成功返回一个拥有根,父对象尚未分配,必须先临时保存它。 let reference = child.load(vm)?; roots.push(reference); constants[*index] = Value::Object(reference); } + // 用目标 VM 的子代码常量重新构造并验证代码,再恢复原位置元数据。 let mut code = EndorCode::with_layout( self.code.bytecode().to_vec(), constants, @@ -60,11 +80,13 @@ impl CompiledCode { self.code.filename(), self.code.first_line(), ); + // 函数签名随模板一同复制,保证装载后仍有类型注解/方法归属信息。 if let Some(signature) = self.code.source_signature() { code = code.with_source_signature(signature.clone())?; } vm.new_code(code) })(); + // 父代码已取得内部边或装载失败后,临时子根都必须释放一次。 for reference in roots { vm.release(reference) .expect("loader owns each temporary code root"); @@ -73,18 +95,23 @@ impl CompiledCode { } } +/// 生成、装载前验证或编码失败的公共包装,附上当前源码跨度并标为 Compilation。 fn error(span: Span, message: impl Into) -> Diagnostic { diagnostic(DiagnosticKind::Compilation, span, message) } +/// compile_ast 在名称分析成功后调用;按根模式生成最终返回协议并收束为已验证模板。 pub(crate) fn generate( root: &AstRoot, source: &SourceUnit, symbols: &SymbolTable, ) -> Result { + // 根生成器借用已经分析好的根布局,模式分支只决定根的指令及返回值。 let mut emitter = Emitter::new(&symbols.root, source); + // 模块生成声明并返回 Nil,表达式返回其值,交互语句返回表达式或 Nil。 match root { AstRoot::Module(program) => { + // 源码声明依原顺序生成,函数调用的前向名称由执行完模块后的环境提供。 for declaration in &program.declarations { emitter.declaration(declaration, symbols)?; } @@ -96,7 +123,9 @@ pub(crate) fn generate( emitter.simple(Opcode::ReturnValue); } AstRoot::Interactive(input) => { + // analyze 已限制交互输入只有一条;这里复用 AST 列表结构。 for statement in &input.statements { + // 交互表达式保留结果作为根返回值,其他语句执行后补 Nil。 if let StmtKind::Expression(expression) = &statement.kind { emitter.expression(expression)?; } else { @@ -110,16 +139,24 @@ pub(crate) fn generate( emitter.finish() } +/// 单个代码单元的生成状态;借用分析布局与源码,持有逻辑指令、常量及尚未解析的跳转。 struct Emitter<'a> { + // 当前根/函数作用域,提供名字、槽索引及源跨度。 scope: &'a Scope, + // 代码来源和文本快照,finish 据此设置文件身份与起始行。 source: &'a SourceUnit, + // 尚未编码扩展前缀的逻辑指令;索引不能直接当作物理 code-unit 偏移。 instructions: Vec, + // 按出现顺序追加常量;嵌套代码在 load 前使用 Nil 占位。 constants: Vec, + // (跳转逻辑指令索引, 目标逻辑边界索引),finish 根据编码宽度计算相对距离。 jumps: Vec<(usize, usize)>, + // (父常量索引, 子模板);Emitter 记录待替换位置,CompiledCode::load 递归装入。 children: Vec<(usize, CompiledCode)>, } impl<'a> Emitter<'a> { + /// generate/function_value 创建当前代码单元的空生成器,各函数独享指令及常量列表。 fn new(scope: &'a Scope, source: &'a SourceUnit) -> Self { Self { scope, @@ -131,23 +168,29 @@ impl<'a> Emitter<'a> { } } + /// 各生成分支追加带参数指令;usize 索引/数量必须能装入 VM 的 u32 操作数。 fn emit(&mut self, opcode: Opcode, argument: usize) -> Result<(), Diagnostic> { + // 在编码前检查数量或索引宽度,超界映射成当前作用域诊断。 let argument = u32::try_from(argument) .map_err(|_| error(self.scope.span, "bytecode operand exceeds u32"))?; self.instructions.push(Instruction::new(opcode, argument)); Ok(()) } + /// 各分支追加不带操作数指令,统一使用零参数编码。 fn simple(&mut self, opcode: Opcode) { self.instructions.push(Instruction::simple(opcode)); } + /// 表达式、声明和补 Nil 路径共用;追加常量并发出 LoadConst,执行时压入值。 fn constant(&mut self, value: Value) -> Result<(), Diagnostic> { + // 下一常量槽位;函数模板登记和常量加载都必须使用追加前的长度。 let index = self.constants.len(); self.constants.push(value); self.emit(Opcode::LoadConst, index) } + /// 声明、属性和全局写入查分析阶段登记的名字;缺项代表编译器内部阶段不一致。 fn name_index(&self, name: &str) -> usize { self.scope .layout @@ -157,24 +200,31 @@ impl<'a> Emitter<'a> { .expect("analysis interns every emitted name") } + /// generate/statement 调用,把顶层函数或类构造成运行时值并写入对应全局名。 fn declaration(&mut self, declaration: &Decl, symbols: &SymbolTable) -> Result<(), Diagnostic> { + // 函数直接构造值,类还需按 BuildClass 协议压入名字、基类、字段表与方法表。 match &declaration.kind { + // 先创建函数值再 StoreGlobal,函数体此时尚未调用。 DeclKind::Function(function) => { self.function_value(function, symbols, None)?; self.emit(Opcode::StoreGlobal, self.name_index(&function.name.text)) } + // 构造类元数据;父类名来自分析阶段校验过的名字表。 DeclKind::Class(class) => { self.constant(Value::Text(class.name.text.clone()))?; + // 未显式指定时查全局 Object,真正的父类型对象由运行时解析。 let base = class .base .as_ref() .map_or("Object", |base| base.text.as_str()); self.emit(Opcode::LoadGlobal, self.name_index(base))?; + // 每字段压入名称和类型名文本,随后 BuildMap 汇总为字段注解映射。 for field in &class.fields { self.constant(Value::Text(field.name.text.clone()))?; self.constant(Value::Text(field.type_name.text.clone()))?; } self.emit(Opcode::BuildMap, class.fields.len())?; + // 每方法压入名称及源码函数值,签名额外记录当前类名。 for method in &class.methods { self.constant(Value::Text(method.name.text.clone()))?; self.function_value(method, symbols, Some(&class.name.text))?; @@ -186,21 +236,27 @@ impl<'a> Emitter<'a> { } } + /// declaration 为普通函数或类方法调用;生成子模板、附源码类型签名并发出 MakeFunction。 + /// owner 仅类方法传入类名,供运行时签名校验使用;当前源码函数没有默认值与闭包捕获。 fn function_value( &mut self, function: &frontend::ast::Function, symbols: &SymbolTable, owner: Option<&str>, ) -> Result<(), Diagnostic> { + // 按源码跨度及名称定位函数分析结果,避免不同类的同名方法混用布局。 let scope = symbols .functions .iter() .find(|scope| scope.span == function.span && scope.name == function.name.text) .expect("analysis creates each function scope"); + // 函数体使用独立生成器,其常量、指令和跳转不混入外层代码。 let mut body = Emitter::new(scope, self.source); + // 按源码顺序生成函数体语句,语义和不可达错误已由 analysis 检查。 for statement in &function.body.statements { body.statement(statement, symbols)?; } + // 末条语句不是显式 return 时补隐式 Nil 返回;末条 if 即使双侧返回也会补兜底出口。 if !matches!( function .body @@ -212,7 +268,9 @@ impl<'a> Emitter<'a> { body.constant(Value::Nil)?; body.simple(Opcode::ReturnValue); } + // 先完成子代码跳转及验证,再把参数/返回类型注解附到模板。 let mut child = body.finish()?; + // 签名保留源码注解名,运行时在环境中解析类型;类方法额外记录 owner。 child.code = child .code .with_source_signature(endor_core::SourceSignature { @@ -225,16 +283,23 @@ impl<'a> Emitter<'a> { method_owner: owner.map(str::to_owned), }) .map_err(|failure| error(function.span, failure.to_string()))?; + // 下一常量槽位;函数模板登记和常量加载都必须使用追加前的长度。 let index = self.constants.len(); + // 子模板与即将追加的 Nil 常量槽成对保存,load 才替换为实际代码句柄。 self.children.push((index, child)); + // 为子代码保留常量槽,load 时用已分配的托管代码对象替换 Nil。 self.constant(Value::Nil)?; // Replaced by a managed code object during load(). + // 源码函数当前没有默认参数或自由 Cell,按零默认值、零捕获构造。 self.instructions.push(Instruction::make_function(0, 0)); Ok(()) } + /// statement 的 let/赋值共用;先生成右值,再按作用域写局部槽或全局名。 fn store_binding(&mut self, name: &str, value: &Expr) -> Result<(), Diagnostic> { self.expression(value)?; + // 函数级目标写 StoreFast,其余根写 StoreGlobal,右值已经在栈顶。 if self.scope.kind == ScopeKind::Function { + // 从预分配局部表取得目标槽,分析阶段保证所有绑定和赋值目标存在。 let index = self .scope .layout @@ -249,48 +314,65 @@ impl<'a> Emitter<'a> { Ok(()) } + /// generate/function_value 及块递归调用,维护语句栈平衡,并记录待 finish 解析的跳转目标。 fn statement(&mut self, statement: &Stmt, symbols: &SymbolTable) -> Result<(), Diagnostic> { + // 语句需消费自身临时值;return 和控制流分别生成出口或跳转。 match &statement.kind { + // 条件为假跳到 else 起点,then 结束无条件越过 else。 StmtKind::If(branch) => { self.expression(&branch.condition)?; + // 保存条件跳转的逻辑索引,当前先用零操作数占位。 let conditional = self.instructions.len(); self.emit(Opcode::PopJumpIfFalse, 0)?; + // 递归生成 then 语句,嵌套跳转同样追加到当前生成器。 for statement in &branch.body.statements { self.statement(statement, symbols)?; } + // 记录 then 尾部无条件跳转位置,稍后指向整个 if 之后。 let end = self.instructions.len(); self.emit(Opcode::JumpForward, 0)?; self.jumps.push((conditional, self.instructions.len())); + // 存在 else 才生成其代码;无 else 时两个出口可指向同一边界。 if let Some(otherwise) = &branch.otherwise { + // else 同样保持语句栈平衡,结束后接 if 的共同后继。 for statement in &otherwise.statements { self.statement(statement, symbols)?; } } self.jumps.push((end, self.instructions.len())); } + // 每次回到条件开头,条件为假退出;记录两个逻辑跳转后交 finish 解析。 StmtKind::While(loop_) => { + // 保存条件求值起点,回边必须重算条件而非仅回到循环体。 let start = self.instructions.len(); self.expression(&loop_.condition)?; + // 保存条件失败的占位跳转位置。 let exit = self.instructions.len(); self.emit(Opcode::PopJumpIfFalse, 0)?; + // 生成循环体,再生成回到条件开头的后向跳转。 for statement in &loop_.body.statements { self.statement(statement, symbols)?; } + // 回边指令的逻辑位置,用于计算从该指令末端到 start 的距离。 let back = self.instructions.len(); self.emit(Opcode::JumpBackward, 0)?; self.jumps.push((back, start)); self.jumps.push((exit, self.instructions.len())); } StmtKind::Binding(binding) => self.store_binding(&binding.name.text, &binding.value)?, + // 普通赋值与 let 共用存储规则;分析阶段已确定目标槽或全局名。 StmtKind::Assignment(assignment) => { self.store_binding(&assignment.name.text, &assignment.value)? } + // StoreAttr 协议要求先压右值,再压接收对象,最后由指令消费两者。 StmtKind::MemberAssignment(assignment) => { self.expression(&assignment.value)?; self.expression(&assignment.object)?; self.emit(Opcode::StoreAttr, self.name_index(&assignment.name.text))?; } + // 返回表达式或补 Nil,然后 ReturnValue 将值交给调用方。 StmtKind::Return(statement) => { + // 显式返回值正常求值;无表达式的 return 需要先压入 Nil。 if let Some(value) = &statement.value { self.expression(value)?; } else { @@ -298,6 +380,7 @@ impl<'a> Emitter<'a> { } self.simple(Opcode::ReturnValue); } + // 语句位置不保留结果,PopTop 使后续语句开始时栈深恢复。 StmtKind::Expression(expression) => { self.expression(expression)?; self.simple(Opcode::PopTop); @@ -307,40 +390,51 @@ impl<'a> Emitter<'a> { Ok(()) } + /// 根、语句或子表达式递归调用;成功路径把一个值留栈,具体对象协议在 VM 执行时处理。 fn expression(&mut self, expression: &Expr) -> Result<(), Diagnostic> { + // 按 AST 种类发出求值序列,每个成功分支最终留一个值。 match &expression.kind { ExprKind::String(value) => self.constant(Value::Text(value.clone()))?, ExprKind::Number(spelling) => self.number(spelling, expression.span)?, + // 使用分析记录,不能在生成时重新猜测局部与全局遮蔽关系。 ExprKind::Name(name) => { + // 按名字及本次使用的跨度找到具体绑定,不混淆同名的不同读取位置。 let reference = self .scope .references .iter() .find(|reference| reference.span == expression.span && &reference.name == name) .expect("analysis resolves every name expression"); + // 局部槽用 LoadFast,全局名字索引用 LoadGlobal。 let (opcode, index) = match reference.binding { NameBinding::Local(index) => (Opcode::LoadFast, index), NameBinding::Global(index) => (Opcode::LoadGlobal, index), }; self.emit(opcode, index)?; } + // 按 VM 调用布局压被调对象、Null 接收者哨兵及各实参;Call 使用实参数量。 ExprKind::Call { callee, arguments } => { self.expression(callee)?; self.simple(Opcode::PushNull); + // 参数按源码顺序求值,每个结果在栈上保留到 Call 消费。 for argument in arguments { self.expression(argument)?; } self.emit(Opcode::Call, arguments.len())?; } + // 仅求值一次等待对象,再由 Await 指令负责停放/轮询;生成器不实现调度。 ExprKind::Await(operand) => { self.expression(operand)?; self.simple(Opcode::Await); } + // 先求接收对象,再用分析已登记的属性名字索引读取。 ExprKind::Member { object, name } => { self.expression(object)?; self.emit(Opcode::LoadAttr, self.name_index(&name.text))?; } + // 纯取负字面量直接合并精确拼写,否则生成操作数及运行时 UnaryNegate。 ExprKind::Unary { operand, .. } => { + // 只折叠数值及连续取负,避免先把超长十进制转换为浮点。 if let Some(spelling) = signed_literal(expression, false) { self.number(&spelling, expression.span)?; } else { @@ -348,6 +442,7 @@ impl<'a> Emitter<'a> { self.simple(Opcode::UnaryNegate); } } + // 左右操作数依次压栈,再由算术或比较指令消费两个值并返回一个值。 ExprKind::Binary { operation, left, @@ -355,11 +450,13 @@ impl<'a> Emitter<'a> { } => { self.expression(left)?; self.expression(right)?; + // 算术使用 BinaryArithmetic 子操作码;其余已知二元操作编码为比较。 match operation { BinaryOp::Add => self.emit(Opcode::BinaryArithmetic, 0)?, BinaryOp::Subtract => self.emit(Opcode::BinaryArithmetic, 1)?, BinaryOp::Multiply => self.emit(Opcode::BinaryArithmetic, 2)?, BinaryOp::Divide => self.emit(Opcode::BinaryArithmetic, 3)?, + // 算术种类已在前四个分支处理,此分支只应剩余比较操作。 _ => self .instructions .push(Instruction::compare(match operation { @@ -373,6 +470,7 @@ impl<'a> Emitter<'a> { })), } } + // 前端能表示符号字面量,但当前运行时无对应值,明确拒绝生成。 ExprKind::Symbol(_) => { return Err(error( expression.span, @@ -383,7 +481,9 @@ impl<'a> Emitter<'a> { Ok(()) } + /// 数值字面量和已合并符号的字面量共用,直接从十进制拼写构造精确数值,不经过 f64。 fn number(&mut self, spelling: &str, span: Span) -> Result<(), Diagnostic> { + // 根据拼写中的小数点选择精确十进制或整数解析,失败保留字面量跨度。 let value = if spelling.contains('.') { EndorNumber::decimal(spelling) } else { @@ -393,12 +493,17 @@ impl<'a> Emitter<'a> { self.constant(Value::Number(value)) } + /// generate/function_value 结束当前单元时调用;迭代修正扩展指令宽度与相对跳转,再验证编码及布局。 fn finish(mut self) -> Result { // Labels refer to logical instructions. Resolve relative code-unit // offsets after EXTENDED_ARG expansion, to a fixed point. + // 跳转距离变大会改变 EXTENDED_ARG 数量,最多迭代六十四轮求稳定布局。 for attempt in 0..64 { + // 记录每条逻辑指令起点及尾部边界的物理 code-unit 偏移,初始零。 let mut offsets = vec![0_usize]; + // 按当前操作数宽度计算一到四个物理单元,包含必要的扩展前缀。 for i in &self.instructions { + // 操作数每多八位需要一个 EXTENDED_ARG 前缀,最终 opcode 本身占一个单元。 let units = if i.argument <= 0xff { 1 } else if i.argument <= 0xffff { @@ -410,30 +515,38 @@ impl<'a> Emitter<'a> { }; offsets.push(offsets.last().unwrap() + units); } + // 本轮若任一跳转操作数改变,就需要重算可能变化的指令宽度。 let mut changed = false; + // 每个目标是逻辑边界,换算时必须使用本轮物理 offsets。 for &(index, target) in &self.jumps { + // 相对距离以完整跳转指令的末端为基准,后向与前向分别相减。 let distance = if self.instructions[index].opcode == Opcode::JumpBackward { offsets[index + 1] - offsets[target] } else { offsets[target] - offsets[index + 1] }; + // 距离也受 u32 编码限制,超界向宿主返回诊断而非截断。 let operand = u32::try_from(distance) .map_err(|_| error(self.scope.span, "jump exceeds u32"))?; changed |= self.instructions[index].argument != operand; self.instructions[index].argument = operand; } + // 所有跳转操作数稳定时,当前宽度与距离自洽,可结束迭代。 if !changed { break; } + // 达到迭代上限仍变化时拒绝产物,不能输出未收敛的跳转。 if attempt == 63 { return Err(error(self.scope.span, "jump layout did not converge")); } } + // 从作用域跨度取起始行,无法定位时使用第一行作为回溯元数据。 let line = self .scope .span .location(self.source.text()) .map_or(1, |location| location.line); + // 编码并验证控制流、栈和布局,错误归到当前作用域,再附源码身份。 let code = EndorCode::from_instructions( &self.instructions, self.constants, @@ -448,17 +561,23 @@ impl<'a> Emitter<'a> { } } +/// expression 的一元负号分支调用;仅对纯数值和连续取负递归合并符号,其余表达式交运行时求值。 +/// negative 记录累计取负奇偶;每遇到一个 Negate 翻转,返回最终精确数字拼写。 fn signed_literal(expression: &Expr, negative: bool) -> Option { + // 只接受纯数值及连续取负链,调用、名字或其他表达式不能静态合并。 match &expression.kind { + // 遇到数值终点,根据累计取负奇偶返回带或不带负号的精确拼写。 ExprKind::Number(spelling) => Some(if negative { format!("-{spelling}") } else { spelling.clone() }), + // 连续负号每层翻转一次,最后统一生成正确符号的数值拼写。 ExprKind::Unary { operation: UnaryOp::Negate, operand, } => signed_literal(operand, !negative), + // 非字面量链返回 None,由 expression 生成实际运行时操作。 _ => None, } } -- 2.52.0 From 5c079c6c583ce23500a7f2411e34e7a1979470c0 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 18:33:08 +0800 Subject: [PATCH 40/62] docs(compiler): explain class and source pipeline integration tests --- crates/compiler/tests/classes.rs | 95 ++++++++++++++++- crates/compiler/tests/pipeline.rs | 170 ++++++++++++++++++++++++++++++ 2 files changed, 264 insertions(+), 1 deletion(-) diff --git a/crates/compiler/tests/classes.rs b/crates/compiler/tests/classes.rs index 71e12e4..3a74e99 100644 --- a/crates/compiler/tests/classes.rs +++ b/crates/compiler/tests/classes.rs @@ -1,3 +1,5 @@ +//! Rust 测试框架驱动的源码类集成测试:从前端/编译器到 VM 的宿主构造、绑定方法、字段检查及 GC。 +//! execute/try_execute 按现有 globals 声明可用宿主名,加载临时代码,执行后统一归还代码根。 use compiler::{CompileOptions, compile_source}; use endor_core::{ ContextNativeFunction, EndorTypeObject, NativeCallable, NativeContext, NativeResult, @@ -7,23 +9,30 @@ 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 { + // 解构 NativeTyped 调用布局;类型对象与标签必须存在,其余切片作为字段参数。 let [ Value::Object(requested), Value::Text(native_label), @@ -32,17 +41,23 @@ impl NativeCallable for WidgetFactory { else { return Err(VmError::InvalidArgument("widget constructor arguments")); }; + // 复制请求类型的完整字段元数据,以便稍后可变访问 Store 并分配实例。 let fields = cx .store() .payload::(*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())?; } @@ -51,14 +66,21 @@ impl NativeCallable for WidgetFactory { } #[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", @@ -66,6 +88,7 @@ fn source_subclasses_preserve_native_allocation_fields_and_nominal_identity() { TypeConstructor::NativeTyped(factory), ) .unwrap(); + // 把引导类型/宿主类型放入源码可访问的环境,再归还各自外部根。 for (name, object) in [ ("Object", roots.object), ("Type", roots.type_type), @@ -75,12 +98,15 @@ fn source_subclasses_preserve_native_allocation_fields_and_nominal_identity() { .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, @@ -90,12 +116,14 @@ fn source_subclasses_preserve_native_allocation_fields_and_nominal_identity() { ), 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") }; @@ -112,30 +140,41 @@ fn source_subclasses_preserve_native_allocation_fields_and_nominal_identity() { 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 { + // 从当前环境条目提取文本键作为编译期已知名;这只声明名称,不复制运行时值。 let names = vm .dict_items(globals) .unwrap() .iter() + // 非文本键不能作为源码标识符,忽略它们而不向编译器提供伪名称。 .filter_map(|(name, _)| { + // 只把文本键转成声明名,其余键留在环境但不参与源码解析。 if let Value::Text(name) = name { Some(name.clone()) } else { @@ -143,24 +182,35 @@ fn try_execute( } }) .collect(); + // 为临时源码添加测试来源身份,错误诊断据此显示文件名与位置。 let source = EndorReader::from_text("", 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 @@ -177,12 +227,14 @@ class Child: Base { 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") }; @@ -192,12 +244,14 @@ class Child: Base { .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") }; @@ -207,27 +261,37 @@ class Child: Base { .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("", text); assert!( compile_source( @@ -243,10 +307,15 @@ fn duplicate_members_and_missing_receivers_fail_before_execution() { } #[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, @@ -280,6 +349,7 @@ class Unknown { value: Missing } "holder = Holder(value)", ParseMode::Interactive, ); + // 从 globals 借用已登记实例;测试不额外持该句柄的拥有根。 let Value::Object(value) = vm .dict_get(globals, &Value::Text("value".into())) .unwrap() @@ -287,7 +357,9 @@ class Unknown { value: Missing } else { panic!("instance") }; + // 记录合法实例当前版本,后续失败的字段写入不得改变它。 let version = vm.object_store().change_version(value).unwrap(); + // 覆盖继承字段、子字段、名义对象字段、未知注解及错误赋值,错误必须在更新前返回。 for source in [ r#"Child("bad", "ok")"#, "Child(9, 2)", @@ -295,6 +367,7 @@ class Unknown { value: Missing } "Unknown(1)", r#"value.count = "bad""#, ] { + // 错误应是类型不匹配或未知注解,不接受其他无关失败掩盖约束问题。 assert!(matches!( try_execute(&mut vm, globals, source, ParseMode::Interactive), Err(VmError::TypeMismatch { .. } | VmError::UnknownAnnotation(_)) @@ -309,25 +382,35 @@ class Unknown { value: Missing } vm.set_attr(value, "count", Value::Number(10.into())) .unwrap(); 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, @@ -337,7 +420,7 @@ fn checked_fields_keep_declaring_self_type_and_can_be_opted_out() { .unwrap(); vm.release(roots.object).unwrap(); vm.release(roots.type_type).unwrap(); - // Object permits nil, so a separate numeric field provides an initial instance. + // Link.next 是递归且不可空的字段;Seed 声明展示继承,后续由可信宿主直接构造旧 Link 实例。 execute( &mut vm, globals, @@ -348,6 +431,7 @@ 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() @@ -355,7 +439,9 @@ class Seed: Link {} 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)) @@ -367,17 +453,24 @@ class Seed: Link {} 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); } diff --git a/crates/compiler/tests/pipeline.rs b/crates/compiler/tests/pipeline.rs index ab644a2..e46c151 100644 --- a/crates/compiler/tests/pipeline.rs +++ b/crates/compiler/tests/pipeline.rs @@ -1,3 +1,5 @@ +//! Rust 测试框架驱动的源码→模板→代码对象→VM 集成验证;成功和错误路径都检查拥有根清理。 +//! compile/module/function/cleanup 为本文件测试复用,编译器本身不执行源码,只有测试显式 load/execute/call。 use compiler::{CompileOptions, CompiledCode, NameBinding, analyze, compile_ast, compile_source}; use endor_core::{ EndorCodeObject, EndorFunctionObject, EndorNumber, ExecutionLimits, NativeFunction, ObjectRef, @@ -5,6 +7,7 @@ use endor_core::{ }; use frontend::{DiagnosticKind, EndorReader, ParseMode, SourceUnit, parse_ast}; +/// 测试共用的源码入口,按 mode 编译,不声明额外宿主名;返回未加载、独立于 VM 堆的模板。 fn compile(text: &str, mode: ParseMode) -> CompiledCode { compile_source( &EndorReader::from_text("", text), @@ -14,16 +17,23 @@ fn compile(text: &str, mode: ParseMode) -> CompiledCode { .unwrap() } +/// 测试共用的模块加载入口:取得代码根、创建环境并执行声明,模块结果应为 Nil;返回两个拥有根供 cleanup。 fn module(vm: &mut Vm, compiled: &CompiledCode) -> (ObjectRef, ObjectRef) { + // 把模板显式装入当前 VM,取得需要归还的代码拥有根。 let code = compiled.load(vm).unwrap(); + // 新建环境拥有根;函数声明和宿主值通过内部边持有,测试结束需清理环境。 let globals = vm.new_dict().unwrap(); + // 执行加载的代码取得拥有返回值;模块为 Nil,表达式则为其实际结果。 let value = vm.execute(code, globals).unwrap(); assert_eq!(value, Value::Nil); + // 归还 execute/call 返回值拥有根;立即值也可以统一通过此入口处理。 vm.release_value(value).unwrap(); (code, globals) } +/// 测试按名称从环境借用函数句柄;不 retain,调用者须保持 globals 或其他拥有图存活。 fn function(vm: &Vm, globals: ObjectRef, name: &str) -> ObjectRef { + // 环境键命中必须是函数对象;这里只借用,不新取得外部根。 let Some(Value::Object(function)) = vm.dict_get(globals, &Value::Text(name.into())).unwrap() else { panic!("missing function {name}") @@ -31,16 +41,23 @@ fn function(vm: &Vm, globals: ObjectRef, name: &str) -> ObjectRef { function } +/// 测试结束时归还代码根、清环境并归还环境根,再收集引用环;预期结果根已由调用者释放。 fn cleanup(vm: &mut Vm, code: ObjectRef, globals: ObjectRef) { + // 归还本轮代码外部根,函数或返回结果需要的内部图仍由各自引用持有。 vm.release(code).unwrap(); + // 清除环境拥有边,打断声明函数/类型到环境的可能引用环。 vm.dict_clear(globals).unwrap(); + // 归还环境初始根;之后只应留下测试明确保存的结果或回调根。 vm.release(globals).unwrap(); + // 清理零外根的引用环,随后对象数应回到零。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] +/// 由 Rust 测试框架调用:逐个加载示例模块并调用 main,验证前向调用、函数值遮蔽、Unicode 返回及精确十进制,并检查代码名和清理。 fn source_files_execute_through_module_and_function_code_objects() { + // 逐项使用独立代码路径核对指定源码与预期值,任一不符都使本测试失败。 for (text, expected) in [ ( include_str!("../examples/hello.endor"), @@ -59,10 +76,15 @@ fn source_files_execute_through_module_and_function_code_objects() { Value::Number(EndorNumber::decimal("-123456789012345678901234567890.00100").unwrap()), ), ] { + // 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。 let compiled = compile(text, ParseMode::Module); + // 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。 let mut vm = Vm::new().unwrap(); + // 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。 let (code, globals) = module(&mut vm, &compiled); + // 从环境借用 main,环境在调用完成前保持存活。 let main = function(&vm, globals, "main"); + // 检查源码函数的代码元数据,证明 main 不是宿主替代回调。 let data = vm.payload::(main, "function").unwrap(); assert_eq!( vm.payload::(data.code(), "code") @@ -71,15 +93,19 @@ fn source_files_execute_through_module_and_function_code_objects() { .name(), "main" ); + // 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。 let result = vm.call(main, &[]).unwrap(); assert_eq!(result, expected); + // 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。 vm.release_value(result).unwrap(); cleanup(&mut vm, code, globals); } } #[test] +/// 由 Rust 测试框架调用:表达式模式直接返回结果,验证文本、大整数、连续负号、前导零和负零均保持精确数值语义。 fn expression_mode_preserves_exact_literals_and_signs() { + // 逐项使用独立代码路径核对指定源码与预期值,任一不符都使本测试失败。 for (text, expected) in [ ( include_str!("../examples/literal.endor"), @@ -95,20 +121,29 @@ fn expression_mode_preserves_exact_literals_and_signs() { Value::Number(EndorNumber::decimal("-0.000").unwrap()), ), ] { + // 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。 let mut vm = Vm::new().unwrap(); + // 表达式模板在当前 VM 单独装载,加载代码拥有根由本轮测试负责归还。 let code = compile(text, ParseMode::Expression).load(&mut vm).unwrap(); + // 新建环境拥有根;函数声明和宿主值通过内部边持有,测试结束需清理环境。 let globals = vm.new_dict().unwrap(); + // 执行加载的代码取得拥有返回值;模块为 Nil,表达式则为其实际结果。 let value = vm.execute(code, globals).unwrap(); assert_eq!(value, expected); + // 归还 execute/call 返回值拥有根;立即值也可以统一通过此入口处理。 vm.release_value(value).unwrap(); cleanup(&mut vm, code, globals); } } #[test] +/// 由 Rust 测试框架调用:直接检查符号表的参数/局部槽、前向全局名绑定与空自由变量表,不加载 VM。 fn scope_analysis_resolves_local_shadowing_and_forward_globals() { + // 当前用例的源码或带来源快照,下面按指定模式解析/编译。 let source = "function main(target: Callable): String { let copy = target return later(copy) } function later(value: String): String { return value }"; + // 先解析 AST,再单独调用名称分析,隔离解析与分析阶段的结果。 let root = parse_ast(source, ParseMode::Module).unwrap(); + // 不创建 VM,直接检查 analyze 输出的稳定参数/局部与全局索引。 let symbols = analyze(&root, &CompileOptions::default()).unwrap(); assert_eq!(symbols.functions[0].layout.parameters, ["target"]); assert_eq!(symbols.functions[0].layout.locals, ["target", "copy"]); @@ -124,6 +159,7 @@ fn scope_analysis_resolves_local_shadowing_and_forward_globals() { NameBinding::Local(1) ] ); + // 当前源码函数不捕获外层局部,所有代码布局的自由变量表都应为空。 assert!( symbols .functions @@ -133,7 +169,9 @@ fn scope_analysis_resolves_local_shadowing_and_forward_globals() { } #[test] +/// 由 Rust 测试框架调用:参数/局部/顶层重复、未声明及未初始化读取都在编译时拒绝,并检查 Unicode 与 CRLF 的诊断行列。 fn semantic_errors_prevent_loading_and_keep_unicode_locations() { + // 每例给出非法源码及应包含的诊断片段,确保失败原因与目标规则对应。 for (text, fragment) in [ ( "function f(): String { return missing }", @@ -165,19 +203,25 @@ fn semantic_errors_prevent_loading_and_keep_unicode_locations() { "before initialization: later", ), ] { + // 保留来源名和原始源码,编译诊断应带有正确位置及错误说明。 let unit = EndorReader::from_text("", text); + // 预期编译失败,之后检查诊断阶段或位置,不能走加载执行路径。 let error = compile_source(&unit, ParseMode::Module, &CompileOptions::default()).unwrap_err(); assert_eq!(error.diagnostic.kind, DiagnosticKind::Semantic); assert!(error.to_string().contains(fragment), "{error}"); } + // 保留来源名和原始源码,编译诊断应带有正确位置及错误说明。 let unit = EndorReader::from_text("<日本語>", "function 表示(): T {\r\n return 未定義\r\n}"); + // 预期编译失败,之后检查诊断阶段或位置,不能走加载执行路径。 let error = compile_source(&unit, ParseMode::Module, &CompileOptions::default()).unwrap_err(); assert_eq!((error.location.line, error.location.column), (2, 10)); assert!(error.to_string().starts_with("<日本語>:2:10:")); } +/// 宿主 NativeFunction 调用的恒等回调;参数为借用值,clone 不增对象根,VM 返回协议负责保留结果。 fn identity(arguments: &[Value]) -> Result { + // 恒等回调只接受一个实参,多参/缺参明确报错而非越界取参数。 if arguments.len() != 1 { return Err(VmError::InvalidArgument("identity needs one argument")); } @@ -185,18 +229,28 @@ fn identity(arguments: &[Value]) -> Result { } #[test] +/// 由 Rust 测试框架调用:验证宿主声明、属性原生调用和拥有对象返回;清空环境后仅结果根保活对象,释放结果后句柄失效。 fn host_globals_member_calls_and_object_results_keep_owned_roots() { + // 编译期仅声明可用宿主名;真正的绑定值仍由 globals 提供。 let options = CompileOptions { globals: vec!["service".into(), "value".into()], }; + // 保留来源名和原始源码,编译诊断应带有正确位置及错误说明。 let unit = EndorReader::from_text("", "service.echo(value)"); + // 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。 let compiled = compile_source(&unit, ParseMode::Expression, &options).unwrap(); + // 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。 let mut vm = Vm::new().unwrap(); + // 分配恒等原生回调作为 service.echo,属性内部边随后保留它。 let native = vm.allocate(NativeFunction::new("echo", identity)).unwrap(); + // 普通宿主对象承载 echo 属性,用于验证成员查找与原生调用链。 let service = vm.new_object().unwrap(); vm.set_attr(service, "echo", Value::Object(native)).unwrap(); + // service.echo 已取得内部边,测试不再额外持原生函数根。 vm.release(native).unwrap(); + // 作为实参及最终返回的托管对象,后续归还原根以检查 VM 返回协议。 let object = vm.new_object().unwrap(); + // 新建环境拥有根;函数声明和宿主值通过内部边持有,测试结束需清理环境。 let globals = vm.new_dict().unwrap(); vm.dict_set( globals, @@ -206,29 +260,45 @@ fn host_globals_member_calls_and_object_results_keep_owned_roots() { .unwrap(); vm.dict_set(globals, Value::Text("value".into()), Value::Object(object)) .unwrap(); + // 环境已持 service,归还宿主初始根。 vm.release(service).unwrap(); + // 环境已持实参对象,归还初始根,之后返回结果必须获得自己的根。 vm.release(object).unwrap(); + // 把模板显式装入当前 VM,取得需要归还的代码拥有根。 let code = compiled.load(&mut vm).unwrap(); + // 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。 let result = vm.execute(code, globals).unwrap(); assert_eq!(result, Value::Object(object)); + // 清除环境拥有边,打断声明函数/类型到环境的可能引用环。 vm.dict_clear(globals).unwrap(); assert!(vm.object(object).is_ok()); + // 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。 vm.release_value(result).unwrap(); assert_eq!(vm.object(object).unwrap_err(), VmError::InvalidHandle); cleanup(&mut vm, code, globals); } #[test] +/// 由 Rust 测试框架调用:同一模板装入两个独立 VM,跨堆句柄被拒绝;重复调用复用已加载代码,不增加堆对象。 fn code_templates_load_independently_and_functions_share_loaded_code() { + // 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。 let compiled = compile(include_str!("../examples/calls.endor"), ParseMode::Module); + // 第一个 VM 单独加载模板,句柄不能被另一个堆接受。 let mut left = Vm::new().unwrap(); + // 第二个 VM 加载同一模板,但得到自身堆专属代码及环境。 let mut right = Vm::new().unwrap(); + // 左堆建立独立模块图,后续重复调用仅访问这份代码。 let (left_code, left_globals) = module(&mut left, &compiled); + // 右堆建立另一份图,用来检查 ForeignHandle 拒绝跨 VM 引用。 let (right_code, right_globals) = module(&mut right, &compiled); assert_eq!(left.object(right_code).unwrap_err(), VmError::ForeignHandle); + // 记录多次调用前堆大小,调用结束不应重建源码函数代码或残留临时图。 let before = left.object_count(); + // 从环境借用 main,环境在调用完成前保持存活。 let main = function(&left, left_globals, "main"); + // 重复运行同一函数三次,每次归还结果,以对象数验证代码复用。 for _ in 0..3 { + // 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。 let result = left.call(main, &[]).unwrap(); left.release_value(result).unwrap(); } @@ -238,14 +308,22 @@ fn code_templates_load_independently_and_functions_share_loaded_code() { } #[test] +/// 由 Rust 测试框架调用:缺参和递归帧上限错误释放执行临时图,同时保留源码位置或限制深度对应的回溯。 fn argument_and_recursion_failures_unwind_vm_frames_and_keep_traceback() { + // 当前用例的源码或带来源快照,下面按指定模式解析/编译。 let source = "\nfunction main(): T { return echo() }\nfunction echo(value: T): T { return value }"; + // 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。 let compiled = compile(source, ParseMode::Module); + // 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。 let mut vm = Vm::new().unwrap(); + // 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。 let (code, globals) = module(&mut vm, &compiled); + // 记录错误调用前对象数,参数/返回检查或帧清理后不能增加存活对象。 let baseline = vm.object_count(); + // 从环境借用 main,环境在调用完成前保持存活。 let main = function(&vm, globals, "main"); + // main 调用 echo 时漏传 value,错误出在被调函数预检,不能残留执行帧或临时根。 assert!(matches!( vm.call(main, &[]), Err(VmError::ArgumentCount { provided: 0, .. }) @@ -254,11 +332,14 @@ fn argument_and_recursion_failures_unwind_vm_frames_and_keep_traceback() { assert_eq!(vm.last_traceback().last().unwrap().first_line, 2); cleanup(&mut vm, code, globals); + // 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。 let mut vm = Vm::new().unwrap(); + // 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。 let (code, globals) = module( &mut vm, &compile("function main(): T { return main() }", ParseMode::Module), ); + // 从环境借用 main,环境在调用完成前保持存活。 let main = function(&vm, globals, "main"); assert_eq!( vm.call_with_limits( @@ -276,8 +357,11 @@ fn argument_and_recursion_failures_unwind_vm_frames_and_keep_traceback() { } #[test] +/// 由 Rust 测试框架调用:编译选项只声明合法名字,不注入 VM 值;空环境执行仍报告 NameNotFound。 fn declared_host_names_are_checked_again_at_runtime() { + // 当前用例的源码或带来源快照,下面按指定模式解析/编译。 let source = EndorReader::from_text("", "missing"); + // 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。 let compiled = compile_source( &source, ParseMode::Expression, @@ -286,8 +370,11 @@ fn declared_host_names_are_checked_again_at_runtime() { }, ) .unwrap(); + // 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。 let mut vm = Vm::new().unwrap(); + // 把模板显式装入当前 VM,取得需要归还的代码拥有根。 let code = compiled.load(&mut vm).unwrap(); + // 新建环境拥有根;函数声明和宿主值通过内部边持有,测试结束需清理环境。 let globals = vm.new_dict().unwrap(); assert_eq!( vm.execute(code, globals), @@ -297,9 +384,13 @@ fn declared_host_names_are_checked_again_at_runtime() { } #[test] +/// 由 Rust 测试框架调用:两次单语句交互共享宿主环境,首次绑定返回 Nil,后续读取返回此前保存值。 fn interactive_submissions_can_share_a_host_owned_namespace() { + // 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。 let mut vm = Vm::new().unwrap(); + // 新建环境拥有根;函数声明和宿主值通过内部边持有,测试结束需清理环境。 let globals = vm.new_dict().unwrap(); + // 两次独立交互提交共享同一个宿主环境;名字声明随提交需求分别提供。 for (text, options, expected) in [ ( "let value = \"保留\"", @@ -314,47 +405,70 @@ fn interactive_submissions_can_share_a_host_owned_namespace() { Value::Text("保留".into()), ), ] { + // 当前用例的源码或带来源快照,下面按指定模式解析/编译。 let source = EndorReader::from_text("", text); + // 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。 let compiled = compile_source(&source, ParseMode::Interactive, &options).unwrap(); + // 把模板显式装入当前 VM,取得需要归还的代码拥有根。 let code = compiled.load(&mut vm).unwrap(); + // 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。 let result = vm.execute(code, globals).unwrap(); assert_eq!(result, expected); + // 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。 vm.release_value(result).unwrap(); + // 归还本轮代码外部根,函数或返回结果需要的内部图仍由各自引用持有。 vm.release(code).unwrap(); } + // 清除环境拥有边,打断声明函数/类型到环境的可能引用环。 vm.dict_clear(globals).unwrap(); + // 归还环境初始根;之后只应留下测试明确保存的结果或回调根。 vm.release(globals).unwrap(); assert_eq!(vm.object_count(), 0); } #[test] +/// 由 Rust 测试框架调用:构造 270 个局部与常量,迫使索引使用扩展操作数,执行返回最后一个值验证编码解码贯通。 fn extended_operands_work_for_large_local_and_constant_tables() { + // 动态拼接大量独立局部声明,测试超过单字节的索引编码。 let mut source = String::from("function main(): Number { "); + // 超过 255 的局部/常量索引需要 EXTENDED_ARG,返回值来自最后一个槽。 for index in 0..270 { source.push_str(&format!("let value{index} = {index} ")); } source.push_str("return value269 }"); + // 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。 let mut vm = Vm::new().unwrap(); + // 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。 let (code, globals) = module(&mut vm, &compile(&source, ParseMode::Module)); + // 从环境借用 main,环境在调用完成前保持存活。 let main = function(&vm, globals, "main"); + // 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。 let result = vm.call(main, &[]).unwrap(); assert_eq!(result, Value::Number(269_i64.into())); + // 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。 vm.release_value(result).unwrap(); cleanup(&mut vm, code, globals); } #[test] +/// 由 Rust 测试框架调用:前端可解析的 Symbol 暂无 VM 值,在编译生成阶段明确失败。 fn unsupported_execution_features_are_compile_errors_instead_of_fake_results() { + // 当前用例的源码或带来源快照,下面按指定模式解析/编译。 let source = EndorReader::from_text("", ".title"); + // 预期编译失败,之后检查诊断阶段或位置,不能走加载执行路径。 let error = compile_source(&source, ParseMode::Expression, &CompileOptions::default()).unwrap_err(); assert_eq!(error.diagnostic.kind, DiagnosticKind::Compilation); } #[test] +/// 由 Rust 测试框架调用:破坏手工 AST 的 UTF-8 边界,验证直接 AST 编译入口先拒绝无效源跨度。 fn compiling_ast_rejects_invalid_source_ranges() { + // 保留含三字节字符的原始快照,用于故意制造 UTF-8 非边界跨度。 let source: SourceUnit = EndorReader::from_text("", "\"日\""); + // 解析合法表达式 AST,再手工修改跨度,仅测试 AST 入口验证。 let mut root = parse_ast(source.text(), ParseMode::Expression).unwrap(); + // 将 end 改到日字的多字节编码内部,语法树内容不变但位置应非法。 if let frontend::ast::AstRoot::Expression(expression) = &mut root { expression.span.end = 2; // Splits the three-byte character. } @@ -362,53 +476,73 @@ fn compiling_ast_rejects_invalid_source_ranges() { } #[test] +/// 由 Rust 测试框架调用:交互裸赋值创建再替换全局,下一次编译从现有环境取得宿主名字,最终读取最新值。 fn bare_assignments_create_and_replace_real_interactive_globals() { + // 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。 let mut vm = Vm::new().unwrap(); + // 新建环境拥有根;函数声明和宿主值通过内部边持有,测试结束需清理环境。 let globals = vm.new_dict().unwrap(); + // 同一环境连续执行创建、替换和读取,后面的编译应看到前面登记的名字。 for text in ["value = \"first\"", "value = \"second\"", "value"] { + // 编译期仅声明可用宿主名;真正的绑定值仍由 globals 提供。 let options = CompileOptions { globals: vm .dict_items(globals) .unwrap() .iter() + // 只允许文本环境键成为源码可用名称,不把非文本键当标识符。 .filter_map(|(key, _)| match key { Value::Text(name) => Some(name.clone()), _ => None, }) .collect(), }; + // 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。 let compiled = compile_source( &EndorReader::from_text("", text), ParseMode::Interactive, &options, ) .unwrap(); + // 把模板显式装入当前 VM,取得需要归还的代码拥有根。 let code = compiled.load(&mut vm).unwrap(); + // 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。 let result = vm.execute(code, globals).unwrap(); + // 只有最后的表达式提交返回文本;赋值提交只产生 Nil。 if text == "value" { assert_eq!(result, Value::Text("second".into())); } + // 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。 vm.release_value(result).unwrap(); + // 归还本轮代码外部根,函数或返回结果需要的内部图仍由各自引用持有。 vm.release(code).unwrap(); } assert_eq!( vm.dict_get(globals, &Value::Text("value".into())).unwrap(), Some(Value::Text("second".into())) ); + // 清除环境拥有边,打断声明函数/类型到环境的可能引用环。 vm.dict_clear(globals).unwrap(); + // 归还环境初始根;之后只应留下测试明确保存的结果或回调根。 vm.release(globals).unwrap(); + // 清理零外根的引用环,随后对象数应回到零。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] +/// 由 Rust 测试框架调用:赋值复用参数及同名局部槽,执行验证更新结果;自读未初始化与重复 let 仍编译失败。 fn assignments_update_parameters_and_allocate_one_local_slot_per_target() { + // 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。 let compiled = compile( "function main(value: String): String { copy = value copy = \"updated\" value = copy return value }", ParseMode::Module, ); + // 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。 let mut vm = Vm::new().unwrap(); + // 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。 let (code, globals) = module(&mut vm, &compiled); + // 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。 let result = vm .call( function(&vm, globals, "main"), @@ -416,6 +550,7 @@ fn assignments_update_parameters_and_allocate_one_local_slot_per_target() { ) .unwrap(); assert_eq!(result, Value::Text("updated".into())); + // 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。 vm.release_value(result).unwrap(); cleanup(&mut vm, code, globals); assert!( @@ -440,11 +575,14 @@ fn assignments_update_parameters_and_allocate_one_local_slot_per_target() { } #[test] +/// 由 Rust 测试框架调用:检查模板保存类型签名,加载后开启运行时检查分别拒绝错误参数与错误返回,错误不泄漏对象。 fn source_signatures_survive_template_load_and_enable_runtime_checks() { + // 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。 let compiled = compile( "function identity(value: Number): Number { return value }\nfunction bad(): Number { return \"wrong\" }", ParseMode::Module, ); + // 检查首个子函数模板的源码签名,尚未加载时也必须保留注解。 let signature = compiled .children() .next() @@ -453,20 +591,28 @@ fn source_signatures_survive_template_load_and_enable_runtime_checks() { .unwrap(); assert_eq!(signature.parameters, ["Number"]); assert_eq!(signature.return_type, "Number"); + // 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。 let mut vm = Vm::new().unwrap(); + // 启用运行时源码签名检查,错误参数在函数体前拒绝,错误返回在出口拒绝。 vm.set_source_type_checks(true).unwrap(); + // 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。 let (code, globals) = module(&mut vm, &compiled); + // 借用已加载的 Number 恒等函数,分别尝试合法数值和非法文本。 let identity = function(&vm, globals, "identity"); assert_eq!( vm.call(identity, &[Value::Number(7.into())]).unwrap(), Value::Number(7.into()) ); + // 开启检查后,String 实参不能通过 Number 参数约束。 assert!(matches!( vm.call(identity, &[Value::Text("wrong".into())]), Err(VmError::TypeMismatch { .. }) )); + // 借用声明返回 Number 但实际返回文本的函数,专门验证返回类型约束。 let bad = function(&vm, globals, "bad"); + // 记录错误调用前对象数,参数/返回检查或帧清理后不能增加存活对象。 let baseline = vm.object_count(); + // bad 的函数体能执行,但出口文本不符合声明的 Number 返回类型。 assert!(matches!( vm.call(bad, &[]), Err(VmError::TypeMismatch { .. }) @@ -476,15 +622,23 @@ fn source_signatures_survive_template_load_and_enable_runtime_checks() { } #[test] +/// 由 Rust 测试框架调用:运行 while/if 与加减得到 -100,再检查精确小数、大整数、有理数和非字面量取负。 fn source_control_flow_and_exact_arithmetic_execute_real_bytecode() { + // 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。 let mut vm = Vm::new().unwrap(); + // 先累加 0..9 再减去 10..19,循环与分支结果应是精确整数 -100。 let text = "function main(): Number { i=0 total=0 while i<20 { if i<10 {total=total+i} else {total=total-i} i=i+1 } return total }"; + // 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。 let (code, globals) = module(&mut vm, &compile(text, ParseMode::Module)); + // 从环境借用 main,环境在调用完成前保持存活。 let main = function(&vm, globals, "main"); + // 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。 let result = vm.call(main, &[]).unwrap(); assert_eq!(result, Value::Number((-100_i64).into())); + // 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。 vm.release_value(result).unwrap(); cleanup(&mut vm, code, globals); + // 逐项使用独立代码路径核对指定源码与预期值,任一不符都使本测试失败。 for (text, expected) in [ ("0.1 + 0.2 == 0.3", Value::Bool(true)), ( @@ -494,29 +648,43 @@ fn source_control_flow_and_exact_arithmetic_execute_real_bytecode() { ("1 / 3 + 1 / 3 + 1 / 3", Value::Number(1_i64.into())), ("-(2 + 3)", Value::Number((-5_i64).into())), ] { + // 表达式模板在当前 VM 单独装载,加载代码拥有根由本轮测试负责归还。 let code = compile(text, ParseMode::Expression).load(&mut vm).unwrap(); + // 新建环境拥有根;函数声明和宿主值通过内部边持有,测试结束需清理环境。 let globals = vm.new_dict().unwrap(); + // 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。 let result = vm.execute(code, globals).unwrap(); assert_eq!(result, expected); + // 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。 vm.release_value(result).unwrap(); + // 归还本轮代码外部根,函数或返回结果需要的内部图仍由各自引用持有。 vm.release(code).unwrap(); + // 归还环境初始根;之后只应留下测试明确保存的结果或回调根。 vm.release(globals).unwrap(); } } #[test] +/// 由 Rust 测试框架调用:长循环体迫使相对跳转扩展,单侧初始化和零次循环后的读取编译失败,合法无限循环受指令总预算限制。 fn control_flow_definite_initialization_large_jumps_and_loop_budgets() { + // 拼接较长 if/while 体,迫使条件出口和回边跨越扩展编码距离。 let mut body = String::from("function main(): Number { i=0 while i<2 { if i==0 {"); + // 重复运行同一函数三次,每次归还结果,以对象数验证代码复用。 for _ in 0..300 { body.push_str("i=i+0 "); } body.push_str("} i=i+1 } return i }"); + // 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。 let mut vm = Vm::new().unwrap(); + // 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。 let (code, globals) = module(&mut vm, &compile(&body, ParseMode::Module)); + // 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。 let result = vm.call(function(&vm, globals, "main"), &[]).unwrap(); assert_eq!(result, Value::Number(2_i64.into())); + // 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。 vm.release_value(result).unwrap(); cleanup(&mut vm, code, globals); + // 两种未确定初始化用例:if 只有一侧赋值,以及 while 可能零次执行。 for text in [ "function f(): Number { if 1 {x=2} return x }", "function f(): Number {while 0 {x=2} return x}", @@ -530,6 +698,7 @@ fn control_flow_definite_initialization_large_jumps_and_loop_budgets() { .is_err() ); } + // 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。 let (code, globals) = module( &mut vm, &compile( @@ -537,6 +706,7 @@ fn control_flow_definite_initialization_large_jumps_and_loop_budgets() { ParseMode::Module, ), ); + // 无限循环没有返回出口,必须由累计指令预算终止,之后 cleanup 验证错误清理。 assert!(matches!( vm.call_with_limits( function(&vm, globals, "main"), -- 2.52.0 From 6da2e54bcfd2135c60ae0053880b4b8c5c3d2c33 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 18:38:22 +0800 Subject: [PATCH 41/62] docs(frontend): explain parsing entry points and source diagnostics --- crates/compiler/src/lib.rs | 2 +- crates/frontend/src/ast.rs | 12 +++++++++++ crates/frontend/src/diagnostic.rs | 35 +++++++++++++++++++++++++++++++ crates/frontend/src/lexer.rs | 24 +++++++++++++++++++++ crates/frontend/src/lib.rs | 14 +++++++++++++ crates/frontend/src/parser.rs | 30 ++++++++++++++++++++++++++ crates/frontend/src/source.rs | 22 +++++++++++++++++++ crates/frontend/src/token.rs | 26 +++++++++++++++++++++++ 8 files changed, 164 insertions(+), 1 deletion(-) diff --git a/crates/compiler/src/lib.rs b/crates/compiler/src/lib.rs index 4b5f72d..83a787f 100644 --- a/crates/compiler/src/lib.rs +++ b/crates/compiler/src/lib.rs @@ -27,7 +27,7 @@ pub struct CompileOptions { /// 宿主及 compiler 集成测试的源码编译入口:按指定模式解析,再校验 AST、解析名称并生成模板。 /// source 提供文件身份与原始文本;mode 决定模块、表达式或交互根;options 声明额外宿主名称。 -/// 解析错误已有源码诊断;后续编译错误在这里附加同一源码的位置和片段,成功不执行任何 VM 指令。 +/// 解析错误已有源码诊断;后续编译错误在这里附加同一源码的来源和行列位置,成功不执行任何 VM 指令。 pub fn compile_source( source: &SourceUnit, mode: ParseMode, diff --git a/crates/frontend/src/ast.rs b/crates/frontend/src/ast.rs index b6cc395..3624a1a 100644 --- a/crates/frontend/src/ast.rs +++ b/crates/frontend/src/ast.rs @@ -1,23 +1,33 @@ +//! compiler 和宿主共用的语法树门面:生成类型、可定制访问器、版本化 JSON 文档及源码位置辅助接口。 //! AST types and walkers generated from the single Endor.asdl schema. //! Runtime objects remain in the objects crate. +// 类型及 Spanned 实现来自 Endor.asdl 对应生成器,避免前端与编辑器各维护一套结构。 mod generated; +// 公开借用节点的遍历协议,文档校验和宿主工具可覆盖访问并积累自己的状态。 pub mod visitor; +// JSON 编解码、schema 和跨度/父子范围校验通过此模块实现。 mod wire; +// 宿主和编译器从 ast 直接引用 Program/Expr/Stmt 等生成类型。 pub use generated::*; +// 文档验证失败使用统一错误类型,不等同于 VM 执行错误。 pub use wire::AstError; use crate::{SourceLocation, Span}; /// A syntax node's half-open byte range in its original UTF-8 source. +/// 生成 AST 节点实现的原文定位协议,编译分析、编辑器和测试用它取得字节范围或源片段。 pub trait Spanned { + /// 具体节点实现返回存储的半开字节范围,不自行读取源码。 fn span(&self) -> Span; + /// 宿主/测试按节点范围借用原文;无效 UTF-8 边界或越界返回 None,不分配字符串。 fn source_text<'a>(&self, source: &'a str) -> Option<&'a str> { self.span().slice(source) } + /// 宿主/测试从对应原文计算起点行列;范围不合法时返回 None。 fn location(&self, source: &str) -> Option { self.span().location(source) } @@ -25,9 +35,11 @@ pub trait Spanned { impl Program { /// Iterate the currently supported top-level function declarations. + /// 前端 AST 测试或宿主只需顶层函数时使用;保持声明顺序,类及其方法不进入此迭代器。 pub fn functions(&self) -> impl Iterator { self.declarations .iter() + // 仅取函数声明借用,类仍保留在 declarations,调用方可另行遍历。 .filter_map(|declaration| match &declaration.kind { DeclKind::Function(function) => Some(function), DeclKind::Class(_) => None, diff --git a/crates/frontend/src/diagnostic.rs b/crates/frontend/src/diagnostic.rs index 2ae2495..82fcd87 100644 --- a/crates/frontend/src/diagnostic.rs +++ b/crates/frontend/src/diagnostic.rs @@ -1,3 +1,4 @@ +//! lexer、parser、AST 与 compiler 共用的位置/错误协议;字节跨度与用户显示的 Unicode 行列分开保存。 use std::error::Error; use std::fmt; @@ -5,55 +6,76 @@ use std::fmt; /// the first and last syntax token. EOF has an empty range at source.len(). #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] +/// 语法节点及 Token 的半开原文字节范围 [start,end),EOF 使用 source.len() 处的空区间。 pub struct Span { + // 包含的起始字节偏移,应落在 UTF-8 字符边界。 pub start: usize, + // 不包含的结束字节偏移,包含两端 Token 之间的注释/空白。 pub end: usize, } impl Span { + /// 词法/语法规则及测试构造范围,仅保存偏移;合法性由 slice 或 AST 校验验证。 pub const fn new(start: usize, end: usize) -> Self { Self { start, end } } /// Safely read this range, rejecting reversed, out-of-bounds or split UTF-8 ranges. + /// AST Spanned::source_text 及文档校验调用;安全拒绝倒序、越界或切断 UTF-8 字符的范围。 pub fn slice(self, source: &str) -> Option<&str> { source.get(self.start..self.end) } + /// AST Spanned::location 和编译器调用,先验证整个范围,再计算起点的显示行列。 pub fn location(self, source: &str) -> Option { self.slice(source)?; Some(source_location(source, self.start)) } + /// parser 组合左右节点及后缀范围时调用;沿用第一个起点和最后一个终点,不另做顺序检查。 pub(crate) fn through(self, last: Self) -> Self { Self::new(self.start, last.end) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// 阶段分类供宿主和测试区分错误发生于分词、语法、名称分析还是生成。 pub enum DiagnosticKind { + // 词法规则无法接受字符或字符串转义等输入。 Lexical, + // Token 已合法,但无法匹配指定根的语法。 Syntax, + // 编译名称/控制流分析拒绝重复、未知或未初始化读取。 Semantic, + // AST 校验或指令生成/验证失败,尚未执行 VM。 Compilation, } /// Lines and Unicode scalar columns are one-based. CRLF counts as one newline. #[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// source_location 输出的一基位置;列按 Unicode 标量计数,CRLF 合并为一个换行。 pub struct SourceLocation { + // 从一开始的逻辑行号,独立 CR 和 LF 均换行,CRLF 只换一次。 pub line: usize, + // 从一开始的 Unicode 标量列,不是字节数、字素簇数或终端显示宽度。 pub column: usize, } #[derive(Debug, Clone, PartialEq, Eq)] +/// 各阶段产生的不带来源快照的错误,SourceUnit::annotate 可补来源和行列。 pub struct Diagnostic { + // 原始失败阶段,便于测试和宿主按类别处理。 pub kind: DiagnosticKind, + // 原源码字节位置,Display 直接显示这个范围。 pub span: Span, + // 面向用户的说明,不含来源名及行列前缀。 pub message: String, + // 语法解析器报告的期望项;词法和编译器辅助构造通常留空。 pub expected: Vec, } impl Diagnostic { + /// lexer 和词法规则共用的失败包装,统一设置 Lexical 类别并保留原文字节范围。 pub(crate) fn lexical(span: Span, message: impl Into) -> Self { Self { kind: DiagnosticKind::Lexical, @@ -63,12 +85,14 @@ impl Diagnostic { } } + /// SourceUnit::annotate 及宿主根据起始偏移计算位置;本入口不校验整个 span,调用方应传对应原文。 pub fn location(&self, source: &str) -> SourceLocation { source_location(source, self.span.start) } } impl fmt::Display for Diagnostic { + /// 未绑定来源的错误显示说明及字节区间;来源行列由 SourceDiagnostic 的 Display 提供。 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { write!( formatter, @@ -80,25 +104,36 @@ impl fmt::Display for Diagnostic { impl Error for Diagnostic {} +/// Span::location 和 Diagnostic::location 共用的纯文本扫描,计算 position 前缀的行列,不执行 IO。 +/// 调用方应提供有效字节边界;此辅助函数只扫描到偏移或 EOF,不负责校验范围。 fn source_location(source: &str, position: usize) -> SourceLocation { + // 空前缀位于第一行第一列,每消费一个普通 Unicode 标量才前进一列。 let mut location = SourceLocation { line: 1, column: 1 }; + // 记录上一个已消费字符是否 CR,使紧随其后的 LF 不再次增加行号。 let mut previous_cr = false; + // char_indices 同时提供字节边界和完整字符,避免把多字节字符算成多列。 for (offset, character) in source.char_indices() { + // 当前字符起点已到目标位置,不能把目标字符本身算进前缀。 if offset >= position { break; } + // 换行重置列;普通字符(包括 tab)仅按一个 Unicode 标量计列。 match character { + // CR 自身就是换行,可能随后与 LF 配成一个换行。 '\r' => { location.line += 1; location.column = 1; } + // CR 已完成换行,这个 LF 不再增加行号。 '\n' if previous_cr => {} + // 独立 LF 换到下一行第一列。 '\n' => { location.line += 1; location.column = 1; } _ => location.column += 1, } + // 每轮都更新,只有紧邻 CR 的 LF 才会被合并。 previous_cr = character == '\r'; } location diff --git a/crates/frontend/src/lexer.rs b/crates/frontend/src/lexer.rs index 41c2d5c..a4e75ae 100644 --- a/crates/frontend/src/lexer.rs +++ b/crates/frontend/src/lexer.rs @@ -1,31 +1,45 @@ +//! parser 及宿主分词入口;每次交给 PEG 一个剩余源码前缀,再把相对跨度换回原文绝对字节位置。 use crate::{Diagnostic, Span, Token, TokenKind}; /// Lex a reader snapshot and preserve provenance in any diagnostic. +/// 宿主对 SourceUnit 分词时调用;失败以快照补来源及行列,成功 Token 仍仅存原文字节跨度。 pub fn lex_source(source: &crate::SourceUnit) -> Result, crate::SourceDiagnostic> { lex(source.text()).map_err(|error| source.annotate(error)) } /// Lex the basic Endorphin draft syntax without reading files or changing /// locale. Positions always refer to bytes in this exact UTF-8 source. +/// parser 的各语法入口及词法测试调用;只消费传入文本,Token 不持有源码字符串。 pub fn lex(source: &str) -> Result, Diagnostic> { + // 游标从原文零偏移开始,PEG 的每次扫描都在 offset 之后的后缀上进行。 let mut lexer = Lexer { source, offset: 0 }; + // 按原输入顺序保存有效 Token,空白与行注释由词法规则跳过。 let mut tokens = Vec::new(); + // next 返回 None 表示跳过末尾 trivia 后到 EOF;任一诊断立即中止整个分词。 while let Some(token) = lexer.next()? { tokens.push(token); } Ok(tokens) } +/// lex 内部的借用游标,原文保持不变,offset 始终对应已消费前缀的字节数。 struct Lexer<'source> { + // 原始完整 UTF-8 文本,供后缀扫描和全局错误位置计算。 source: &'source str, + // 下次扫描的原文起点,包含此前已跳过的空白/注释。 offset: usize, } impl Lexer<'_> { + /// lex 循环调用;分别处理 PEG 自身失败和词法动作返回的 Diagnostic,二者位置都换回全局偏移。 fn next(&mut self) -> Result, Diagnostic> { + // 保存本轮起点,不能用后面已经前进的 offset 修正当前 Token 或错误。 let base = self.offset; + // PEG 返回消费字节数与 Result>;规则错误作为值保留,避免回溯改认其他 Token。 let (consumed, token) = lexical::next(&self.source[base..]).map_err(|error| { + // PEG 自身报错位置相对于后缀,加 base 得到完整原文偏移。 let start = base + error.location.offset; + // 覆盖当前一个完整 UTF-8 字符;EOF 没有字符,使用空范围。 let end = start + self.source[start..] .chars() @@ -33,8 +47,10 @@ impl Lexer<'_> { .map_or(0, char::len_utf8); Diagnostic::lexical(Span::new(start, end), "unexpected source character") })?; + // consumed 包括本轮 trivia 和 Token;词法错误随后返回给 lex,不继续扫描。 self.offset += consumed; token + // 成功且非 EOF 的 Token 才需要把两个端点加回原文起点。 .map(|token| { token.map(|mut token| { token.span.start += base; @@ -42,6 +58,7 @@ impl Lexer<'_> { token }) }) + // 词法动作返回的错误同样使用后缀坐标;转换后与 Token 遵循同一位置协议。 .map_err(|mut error| { error.span.start += base; error.span.end += base; @@ -50,11 +67,17 @@ impl Lexer<'_> { } } +/// grammar.peg 的字符串 primary 调用,raw 已去掉引号且转义由 lexer.peg 验证,只解码四种受支持转义。 pub(crate) fn decode_string(raw: &str) -> String { + // AST 持有解码文本,和 Token 所指向的带引号原文分开保存。 let mut result = String::new(); + // 按 Unicode 字符遍历,普通多字节字符不做额外转换。 let mut characters = raw.chars(); + // 遇反斜杠消耗下一个转义字符,其他字符原样追加。 while let Some(character) = characters.next() { + // lexer 已保证反斜杠后有合法转义;缺字符或未知字符表示内部阶段契约被破坏。 if character == '\\' { + // 只支持换行、制表、双引号和反斜杠,不解释额外 Unicode/数字转义。 result.push(match characters.next().expect("lexer validated escape") { 'n' => '\n', 't' => '\t', @@ -69,4 +92,5 @@ pub(crate) fn decode_string(raw: &str) -> String { result } +// 编译时引入 lexical::next 的 rust-peg 规则实现;运行时由 Lexer::next 调用。 include!("lexer.peg"); diff --git a/crates/frontend/src/lib.rs b/crates/frontend/src/lib.rs index 4528d88..d7df9d0 100644 --- a/crates/frontend/src/lib.rs +++ b/crates/frontend/src/lib.rs @@ -1,23 +1,37 @@ +//! 宿主、编译器与测试共用的前端门面:读取不可变源码快照,分词并构造拥有文本的 AST。 //! UTF-8 source reader and rust-peg rules for lexing and token-based parsing. //! Produces syntax only; it does not resolve types, generate code or execute VM //! operations. AST data owns its text and does not borrow the source buffer. +// 公开语法树、访问器及 JSON 文档接口,供编译器分析和编辑器传输使用。 pub mod ast; +// 统一维护半开字节跨度、Unicode 行列及阶段错误,避免词法/语法各自计算位置。 mod diagnostic; +// 把 UTF-8 文本转成 Token;rust-peg 词法规则由该模块引入。 mod lexer; +// 把 Token 序列转成模块、表达式或交互根,不执行类型解析和 VM 操作。 mod parser; +// 从文本、流、文件或宿主提供的 endor URI 读取原样 UTF-8 快照。 mod reader; +// 保存源码来源/版本标签及文本,错误附加来源和已经计算的行列位置。 mod source; +// Token 只存种类与原文跨度,具体文本由解析器从源码读取。 mod token; +// 宿主和编译器可沿用同一套位置与诊断数据结构。 pub use diagnostic::{Diagnostic, DiagnosticKind, SourceLocation, Span}; +// 同时公开裸文本和保留 SourceUnit 来源的分词入口。 pub use lexer::{lex, lex_source}; +// 公开各语法根及来源包装入口;ParseMode 选择语法单位,不维护 REPL 会话。 pub use parser::{ ParseMode, parse, parse_ast, parse_expression, parse_source, parse_source_ast, parse_source_expression, }; +// 宿主实现 EndorSourceProvider 掌握 URI 授权与版本选择,前端只消费返回字节。 pub use reader::{ EndorReader, EndorSourceProvider, ResolvedEndorSource, SourceReadError, SourceReadErrorKind, }; +// 源码快照和带来源诊断供跨读取/解析/编译阶段传递。 pub use source::{SourceDiagnostic, SourceOrigin, SourceUnit}; +// 公开词法输出,供解析测试及直接 Token 使用者检查种类与字节位置。 pub use token::{Token, TokenKind}; diff --git a/crates/frontend/src/parser.rs b/crates/frontend/src/parser.rs index 6722bf4..9fb29b2 100644 --- a/crates/frontend/src/parser.rs +++ b/crates/frontend/src/parser.rs @@ -1,3 +1,4 @@ +//! compiler 与宿主使用的 Token 解析门面;grammar.peg 构造拥有文本的 AST,类型名及名称绑定留待编译分析。 use crate::ast::{ Assignment, AstRoot, BinaryOp, Binding, Block, Class, Decl, DeclKind, Expr, ExprKind, Function, IfStatement, InteractiveInput, MemberAssignment, Name, Parameter, Program, ReturnStatement, @@ -8,17 +9,25 @@ use crate::{Diagnostic, DiagnosticKind, Span, Token, TokenKind, lex}; /// Select the syntax root without implying any execution or REPL session. #[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// parse_ast/parse_source_ast 调用方选择的语法根,不表示开启执行器或 REPL 状态。 pub enum ParseMode { + // 一个或多个顶层函数/类声明。 Module, + // 恰好一个完整表达式,不能附带其他语句。 Expression, + // 一个完整的交互语句,可为声明、绑定、赋值、控制流或表达式。 Interactive, } +/// compile_source 和语法测试调用,按 mode 返回 AstRoot;词法失败先返回,语法失败转换为原文跨度诊断。 pub fn parse_ast(source: &str, mode: ParseMode) -> Result { + // 模块/表达式复用专门入口,交互入口直接调用对应 PEG 根规则。 match mode { ParseMode::Module => parse(source).map(AstRoot::Module), ParseMode::Expression => parse_expression(source).map(AstRoot::Expression), + // 语法只收一个完整提交,不负责多次提交共享环境或未完成输入提示。 ParseMode::Interactive => { + // 先分词,保留 Token 的原文跨度,供语法错误从 Token 索引映射回字节。 let tokens = lex(source)?; syntax::interactive(&tokens, source) .map(AstRoot::Interactive) @@ -27,6 +36,7 @@ pub fn parse_ast(source: &str, mode: ParseMode) -> Result { } } +/// compiler 与宿主的快照入口;语法根由 mode 决定,错误增加该快照的来源和行列。 pub fn parse_source_ast( source: &crate::SourceUnit, mode: ParseMode, @@ -35,10 +45,12 @@ pub fn parse_source_ast( } /// Parse a reader snapshot, retaining its origin and resolved revision on error. +/// 宿主从 Reader 快照解析模块时调用,保留来源及 Endor 版本身份到错误包装。 pub fn parse_source(source: &crate::SourceUnit) -> Result { parse(source.text()).map_err(|error| source.annotate(error)) } +/// 宿主从快照解析一个表达式时调用,与 parse_expression 共享语法,仅增加错误来源。 pub fn parse_source_expression( source: &crate::SourceUnit, ) -> Result { @@ -47,21 +59,28 @@ pub fn parse_source_expression( /// Parse one or more typed function or class declarations. This is a syntax /// operation: type names and name references are retained without resolution. +/// parse_ast 的 Module 分支、parse_source 和语法测试调用;只构造声明树,不注册 VM 全局值。 pub fn parse(source: &str) -> Result { + // 词法诊断原样传播;语法 PEG 处理的是 Token 序列,不直接按 UTF-8 字节推进。 let tokens = lex(source)?; syntax::program(&tokens, source).map_err(|error| diagnostic(error, &tokens, source.len())) } /// Parse exactly one complete expression, useful independently of declarations. +/// parse_ast 的 Expression 分支、快照包装及表达式测试调用;要求整份 Token 输入被消费。 pub fn parse_expression(source: &str) -> Result { + // 词法诊断原样传播;语法 PEG 处理的是 Token 序列,不直接按 UTF-8 字节推进。 let tokens = lex(source)?; syntax::expression(&tokens, source).map_err(|error| diagnostic(error, &tokens, source.len())) } +/// 各 PEG 语法根的 map_err 调用,把失败 Token 索引映射为原文范围,并保留期望项列表。 fn diagnostic(error: peg::error::ParseError, tokens: &[Token], end: usize) -> Diagnostic { + // 命中 Token 则定位该 Token,EOF 或越界索引使用原文尾部空范围。 let span = tokens .get(error.location) .map_or(Span::new(end, end), |token| token.span); + // PEG 期望项转成拥有字符串,既写入可读消息,也保留供宿主程序化显示。 let expected: Vec = error.expected.tokens().map(str::to_owned).collect(); Diagnostic { kind: DiagnosticKind::Syntax, @@ -71,6 +90,7 @@ fn diagnostic(error: peg::error::ParseError, tokens: &[Token], end: usize } } +/// grammar.peg 的交互绑定/局部绑定规则调用,复用 Binding 自身范围包装成语句节点。 fn binding_statement(binding: Binding) -> Stmt { Stmt { span: binding.span, @@ -78,6 +98,7 @@ fn binding_statement(binding: Binding) -> Stmt { } } +/// grammar.peg 的优先级规则调用,把已解析左右操作数包装为二元节点;跨度覆盖两端及中间原文。 fn binary(left: Expr, operation: BinaryOp, right: Expr) -> Expr { Expr { span: left.span.through(right.span), @@ -89,17 +110,25 @@ fn binary(left: Expr, operation: BinaryOp, right: Expr) -> Expr { } } +/// grammar.peg 暂存一个后缀,postfix 按源码从左到右把链式成员/调用折叠成 AST。 enum Suffix { + // 点后属性名节点,自身含原文字节跨度。 Member(Name), + // 参数表达式与右括号范围,后者用于延长整个调用节点的跨度。 Call(Vec, Span), } +/// grammar.peg 的 class_member 返回中间分类,再由 class 规则分别收集字段和方法。 enum ClassMember { + // 字段使用名称加类型名结构,与 Parameter 共用形状。 Field(Parameter), + // 类方法沿用函数 AST;显式 self 等约束由 compiler::analyze 检查。 Method(Function), } +/// grammar.peg 的 postfix_expression fold 调用;每步把前一个结果作为新的接收对象或被调表达式。 fn postfix(expression: Expr, suffix: Suffix) -> Expr { + // 成员节点延长到属性名末尾,调用节点延长到右括号,保持连续后缀完整跨度。 match suffix { Suffix::Member(name) => Expr { span: expression.span.through(name.span), @@ -118,4 +147,5 @@ fn postfix(expression: Expr, suffix: Suffix) -> Expr { } } +// 编译时引入 syntax 根规则;上述公开入口与 AST 构造辅助函数在规则中互相调用。 include!("grammar.peg"); diff --git a/crates/frontend/src/source.rs b/crates/frontend/src/source.rs index 29408b5..67e92f5 100644 --- a/crates/frontend/src/source.rs +++ b/crates/frontend/src/source.rs @@ -1,3 +1,4 @@ +//! Reader 产生 SourceUnit,lexer/parser/compiler 借用其文本;错误在 annotate 时固化来源及行列。 use std::error::Error; use std::fmt; use std::path::PathBuf; @@ -7,15 +8,22 @@ use crate::{Diagnostic, SourceLocation}; /// Source provenance, independent of filesystem layout or VM heap handles. /// Endor revisions are opaque labels supplied by the host, not verified hashes. #[derive(Debug, Clone, PartialEq, Eq)] +/// Reader 选择的来源身份,诊断和代码文件名显示使用;Endor revision 是宿主标签,不代表已验证哈希。 pub enum SourceOrigin { + // 内存文本的宿主显示名,不表示磁盘路径。 Text { name: String }, + // 已读到 EOF 的字节流显示名,流本身不再存于快照。 Stream { name: String }, + // 实际读取时保存的路径,不要求规范化为绝对路径。 File { path: PathBuf }, + // 经宿主解析的 URI 与所选版本身份,前端不推断其命名空间或哈希算法。 Endor { uri: String, revision: String }, } impl fmt::Display for SourceOrigin { + /// 诊断与生成代码文件名调用 Display,按来源种类生成可读身份文本。 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + // 文本/流显示名称,文件显示路径,Endor 来源附带版本标签。 match self { Self::Text { name } | Self::Stream { name } => formatter.write_str(name), Self::File { path } => write!(formatter, "{}", path.display()), @@ -27,25 +35,33 @@ impl fmt::Display for SourceOrigin { /// An owned, immutable UTF-8 snapshot. Reader adapters preserve the text exactly /// so token byte offsets still refer to the original decoded source. #[derive(Debug, Clone, PartialEq, Eq)] +/// EndorReader 构造的拥有型 UTF-8 快照;原文不规范化,以保持 Token 和 AST 字节跨度对应原输入。 pub struct SourceUnit { + // 来源身份与文本一起保存,供错误和代码元数据使用。 origin: SourceOrigin, + // 已验证 UTF-8 的原始文本,解析阶段只借用它,不修改换行或空白。 text: String, } impl SourceUnit { + /// Reader 适配器完成解码后调用;将来源及文本移入快照,不再读取外部数据。 pub(crate) fn new(origin: SourceOrigin, text: String) -> Self { Self { origin, text } } + /// 编译器生成文件名、宿主检查来源时借用身份,保留 Endor 的已解析版本。 pub fn origin(&self) -> &SourceOrigin { &self.origin } + /// lexer/parser/compiler 及位置校验借用 UTF-8 文本,返回生命周期依附当前快照。 pub fn text(&self) -> &str { &self.text } /// Bind a diagnostic to this snapshot before discarding its source buffer. + /// lex_source、parse_source 系列及 compile_source 在失败时调用,把字节诊断绑定到此来源和行列。 + /// 返回值保存来源、位置及原诊断,不保存源码全文;后续无需再次读源即可显示基本错误。 pub fn annotate(&self, diagnostic: Diagnostic) -> SourceDiagnostic { SourceDiagnostic { location: diagnostic.location(&self.text), @@ -56,13 +72,18 @@ impl SourceUnit { } #[derive(Debug, Clone, PartialEq, Eq)] +/// 带来源及预计算位置的阶段错误,由 SourceUnit::annotate 创建,交给宿主显示或追查底层 Diagnostic。 pub struct SourceDiagnostic { + // 错误对应的来源,独立于原快照生命周期。 pub origin: SourceOrigin, + // annotate 当时根据原文计算的一基行列,后续 Display 不重新计算。 pub location: SourceLocation, + // 原始字节跨度、阶段、说明及期望项,保留给程序化处理与 Error::source。 pub diagnostic: Box, } impl fmt::Display for SourceDiagnostic { + /// 宿主显示错误时输出“来源:行:列:说明”,跨度及期望项仍可从 diagnostic 单独读取。 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { write!( formatter, @@ -73,6 +94,7 @@ impl fmt::Display for SourceDiagnostic { } impl Error for SourceDiagnostic { + /// 标准错误链使用者调用,暴露原始阶段诊断作为本错误的直接原因。 fn source(&self) -> Option<&(dyn Error + 'static)> { Some(self.diagnostic.as_ref()) } diff --git a/crates/frontend/src/token.rs b/crates/frontend/src/token.rs index 6b2a129..df2b55b 100644 --- a/crates/frontend/src/token.rs +++ b/crates/frontend/src/token.rs @@ -1,35 +1,61 @@ +//! lexer 产生、parser 消费的轻量 Token;文本通过 span 从原 SourceUnit 借取,不在 Token 中复制。 use crate::Span; /// Text is located by span in the original source. Names stay contextual: the /// lexer does not turn syntax words into globally reserved keywords. #[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// lexer.peg 输出的种类;语法关键字仍是 Name,由 grammar.peg 按原文上下文识别。 pub enum TokenKind { + // Unicode 名称,包含上下文关键字;这里只区分词形,不决定语义用途。 Name, + // 无符号十进制整数/小数拼写;负号是单独 Token,精确值由编译器构造。 Number, /// Span includes quotes. Escapes have been checked, not decoded. + // 含引号的字符串范围,转义已校验但未解码;解析时再生成拥有文本。 String, + // 左圆括号,用于参数、调用或括号表达式。 LeftParen, + // 右圆括号,调用范围在它之后结束。 RightParen, + // 左花括号,开始类或语句块。 LeftBrace, + // 右花括号,结束类或语句块。 RightBrace, + // 类型注解及继承声明的冒号。 Colon, + // 参数和实参间的分隔符。 Comma, + // 成员访问或符号字面量的点。 Dot, + // 单等号赋值,不与双等号比较混淆。 Equal, + // 双等号相等比较。 EqualEqual, + // 不等比较。 NotEqual, + // 小于比较。 Less, + // 小于等于比较。 LessEqual, + // 大于比较。 Greater, + // 大于等于比较。 GreaterEqual, + // 加法操作符。 Plus, + // 减法或一元取负,由语法上下文区分。 Minus, + // 乘法操作符。 Star, + // 除法操作符;双斜杠行注释在 trivia 阶段跳过。 Slash, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// 一次词法输出;Token 可复制,但只保存种类和字节范围,调用方须保留匹配的原文以读取内容。 pub struct Token { + // parser 按此选择语法分支,名称关键字额外检查原文。 pub kind: TokenKind, + // 原始 UTF-8 输入的半开范围,不含前面被跳过的空白/注释。 pub span: Span, } -- 2.52.0 From 3a75476bc59cdad72f6853ba80bd6a4cd6032c16 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 18:42:54 +0800 Subject: [PATCH 42/62] docs(frontend): explain source readers and PEG grammar rules --- crates/frontend/src/grammar.peg | 63 +++++++++++++++++++++++++++++++++ crates/frontend/src/lexer.peg | 23 ++++++++++++ crates/frontend/src/reader.rs | 35 ++++++++++++++++++ 3 files changed, 121 insertions(+) diff --git a/crates/frontend/src/grammar.peg b/crates/frontend/src/grammar.peg index 914faa3..9e067d8 100644 --- a/crates/frontend/src/grammar.peg +++ b/crates/frontend/src/grammar.peg @@ -1,79 +1,110 @@ +// parser.rs 编译时引入,生成 syntax 的 Token 解析规则;source 是这些 Token 对应的原始 UTF-8 文本。 +// 规则只构造拥有文本及原文跨度的 AST,不解析名称/类型,也不执行函数或操作 VM。 peg::parser! { + // parse/parse_expression/parse_ast 的交互分支调用相应公开根,默认要求完整消费 Token 输入。 grammar syntax(source: &str) for [Token] { + // 各语法上下文按原文精确匹配 Name;词法器不把这些词全局保留为独立关键字种类。 rule keyword(word: &str) -> Span = [Token { kind: TokenKind::Name, span } if &source[span.start..span.end] == word] { span } + // function 规则的开头;quiet 隐藏内部 Token 细节,失败期望显示可读的 function。 rule function_keyword() -> Span = quiet!{keyword("function")} / expected!("'function'") + // binding 的开头,同样提供稳定可读的期望项。 rule let_keyword() -> Span = quiet!{keyword("let")} / expected!("'let'") + // return_statement 的开头及 local_statement 的负向前瞻,避免 return 被普通表达式语句消费。 rule return_keyword() -> Span = quiet!{keyword("return")} / expected!("'return'") + // 声明、类型名及表达式引用共用;从 Token 范围复制名称文本,AST 不借用原源码缓冲。 rule name() -> Name = span:quiet!{[Token { kind: TokenKind::Name, span }] { span }} { Name { text: source[span.start..span.end].into(), span } } / expected!("identifier") + // 函数参数、调用及括号表达式复用左括号匹配,返回范围供组合节点使用。 rule lparen() -> Span = quiet!{[Token { kind: TokenKind::LeftParen, span }] { span }} / expected!("'('") + // 参数/调用/括号表达式复用右括号匹配,其终点可成为完整表达式终点。 rule rparen() -> Span = quiet!{[Token { kind: TokenKind::RightParen, span }] { span }} / expected!("')'") + // 类和 block 的左边界,缺失时报告可读的左花括号期望项。 rule lbrace() -> Span = quiet!{[Token { kind: TokenKind::LeftBrace, span }] { span }} / expected!("'{'") + // 类和 block 的右边界,节点跨度包含这个闭合括号。 rule rbrace() -> Span = quiet!{[Token { kind: TokenKind::RightBrace, span }] { span }} / expected!("'}'") + // 类型注解和类继承共用冒号,不额外构造 AST 节点。 rule colon() = quiet!{[Token { kind: TokenKind::Colon, .. }]} / expected!("':'") + // 参数/实参列表分隔;列表组合器决定空列表及分隔规则。 rule comma() = quiet!{[Token { kind: TokenKind::Comma, .. }]} / expected!("','") + // 绑定和赋值共用单等号 Token,不接受相等比较 Token。 rule equal() = quiet!{[Token { kind: TokenKind::Equal, .. }]} / expected!("'='") + // 成员后缀和符号字面量共用点,返回其范围以保留符号起点。 rule dot() -> Span = quiet!{[Token { kind: TokenKind::Dot, span }] { span }} / expected!("'.'") + // 加法优先级层消费 Plus Token。 rule plus() = [Token { kind: TokenKind::Plus, .. }] + // 减法及一元取负共用 Minus,前缀负号需要它的跨度作为节点起点。 rule minus() -> Span = [Token { kind: TokenKind::Minus, span }] { span } + // 乘法优先级层消费 Star Token。 rule star() = [Token { kind: TokenKind::Star, .. }] + // 除法优先级层消费 Slash,注释已在词法 trivia 中跳过。 rule slash() = [Token { kind: TokenKind::Slash, .. }] + // parse 的模块入口;至少一个顶层函数或类声明,默认拒绝剩余 Token。 pub rule program() -> Program = declarations:declaration()+ { + // 声明列表由 + 保证非空,根范围从首声明起点延伸到末声明终点。 let span = declarations[0].span.through(declarations.last().unwrap().span); Program { declarations, span } } + // program/interactive_statement 的声明分支;函数直接包装,类则收集基类及成员。 rule declaration() -> Decl = function:function() { Decl { span: function.span, kind: DeclKind::Function(function) } } / start:keyword("class") name:name() base:(colon() base:name() { base })? lbrace() members:class_member()* end:rbrace() { + // 按字段出现顺序收集字段参数结构,类的布局稍后由编译器及 VM 解释。 let mut fields = Vec::new(); + // 按方法出现顺序收集函数结构,与字段分开存储但不改变各自顺序。 let mut methods = Vec::new(); + // 解析阶段先统一取得成员,再按中间分类分发到两份 AST 列表。 for member in members { + // 字段和方法保留各自原节点及跨度,不在这里检查成员重名或 self。 match member { ClassMember::Field(field) => fields.push(field), ClassMember::Method(method) => methods.push(method), } } + // 类范围包括 class 开头与闭合花括号,中间空白/注释仍属于原文范围。 let span = start.through(end); Decl { span, kind: DeclKind::Class(Class { name, base, fields, methods, span }) } } + // declaration 的类体调用,先尝试完整方法,再尝试名称加类型的字段。 rule class_member() -> ClassMember = function:function() { ClassMember::Method(function) } / field:parameter() { ClassMember::Field(field) } + // parse_ast 的交互入口;恰好一个完整语句包装为列表,不管理多次提交的环境。 pub rule interactive() -> InteractiveInput = statement:interactive_statement() { InteractiveInput { span: statement.span, statements: vec![statement] } } + // 交互允许声明、控制流、let、属性/普通赋值及表达式,不提供根级 return。 rule interactive_statement() -> Stmt = declaration:declaration() { Stmt { span: declaration.span, kind: StmtKind::Declaration(declaration) } @@ -86,24 +117,30 @@ peg::parser! { Stmt { span: expression.span, kind: StmtKind::Expression(expression) } } + // 顶层和类方法共用函数语法,显式参数类型、返回类型及函数体;self 合法性留给分析。 rule function() -> Function = start:function_keyword() name:name() lparen() parameters:(parameter() ** comma()) rparen() colon() return_type:name() body:block() { Function { span: start.through(body.span), name, parameters, return_type, body } } + // 函数参数或类字段共用名称/类型名节点,语法只保留注解文本而不解析类型身份。 rule parameter() -> Parameter = name:name() colon() type_name:name() { Parameter { span: name.span.through(type_name.span), name, type_name } } + // function 和控制流共用块语法,普通语句后可接一个末尾 return;return 当前要求表达式。 rule block() -> Block = start:lbrace() locals:local_statement()* returned:return_statement()? end:rbrace() { + // 先保留普通语句序列,下面仅在存在末尾 return 时追加,保持源码顺序。 let mut statements = locals; + // 可选 return 不会被当作普通表达式消费,也不能后接其他块内语句。 if let Some(returned) = returned { statements.push(returned); } Block { statements, span: start.through(end) } } + // 块内语句分派;成员赋值先于普通赋值,最后表达式分支显式排除 return 前缀。 rule local_statement() -> Stmt = control:control_statement() { control } / binding:binding() { binding_statement(binding) } @@ -113,26 +150,34 @@ peg::parser! { Stmt { span: expression.span, kind: StmtKind::Expression(expression) } } + // 交互或块内解析 if/else、while,条件和嵌套 block 都保留为语法树,尚不推断初始化。 rule control_statement() -> Stmt = start:keyword("if") condition:expression() body:block() otherwise:(keyword("else") body:block() { body })? { + // if 范围有 else 时延伸到 else 块末尾,否则只延伸到 then 块末尾。 let span=start.through(otherwise.as_ref().map_or(body.span,|b|b.span)); Stmt {span,kind:StmtKind::If(IfStatement {condition,body,otherwise,span})} } / start:keyword("while") condition:expression() body:block() { + // while 范围覆盖关键字、条件及整个循环体。 let span=start.through(body.span); Stmt {span,kind:StmtKind::While(WhileStatement {condition,body,span})} } + // 交互或局部普通赋值;允许语法上未声明目标,具体全局/局部规则由 compiler::analyze 决定。 rule assignment() -> Stmt = name:name() equal() value:expression() { + // 赋值语句的原文范围从目标名延伸到右值末尾。 let span = name.span.through(value.span); Stmt { span, kind: StmtKind::Assignment(Assignment { name, value, span }) } } + // 先解析后缀目标再检查其最外层确为 Member;错误目标使本规则失败,不执行对象写入。 rule member_assignment() -> Stmt = target:postfix_expression() equal() value:expression() {? + // 属性赋值保留整个接收对象/属性链及右值的原文范围。 let span = target.span.through(value.span); + // 只有成员访问可拆成接收对象与字段名,裸名字留给普通赋值分支识别。 match target.kind { ExprKind::Member { object, name } => Ok(Stmt { span, kind: StmtKind::MemberAssignment(MemberAssignment { object: *object, name, value, span }), @@ -141,18 +186,23 @@ peg::parser! { } } + // 交互/局部 let 绑定,范围包括 let 关键字;重复名和右值初始化检查在后续语义分析。 rule binding() -> Binding = start:let_keyword() name:name() equal() value:expression() { Binding { span: start.through(value.span), name, value } } + // block 的可选末尾返回,语法要求一个表达式,构造 ReturnStatement 的 Some 值。 rule return_statement() -> Stmt = start:return_keyword() value:expression() { + // return 范围从关键字到返回表达式末尾,包括其间空白。 let span = start.through(value.span); Stmt { span, kind: StmtKind::Return(ReturnStatement { value: Some(value), span }) } } + // parse_expression 公开入口及各子表达式共用;precedence 层由低到高组织,二元操作在同层左结合。 pub rule expression() -> Expr = precedence! { + // 比较层优先级最低;六种比较均复用 binary 包装左右节点和范围。 left:(@) [Token {kind:TokenKind::EqualEqual,..}] right:@ {binary(left,BinaryOp::Equal,right)} left:(@) [Token {kind:TokenKind::NotEqual,..}] right:@ {binary(left,BinaryOp::NotEqual,right)} left:(@) [Token {kind:TokenKind::Less,..}] right:@ {binary(left,BinaryOp::Less,right)} @@ -160,42 +210,55 @@ peg::parser! { left:(@) [Token {kind:TokenKind::Greater,..}] right:@ {binary(left,BinaryOp::Greater,right)} left:(@) [Token {kind:TokenKind::GreaterEqual,..}] right:@ {binary(left,BinaryOp::GreaterEqual,right)} -- + // 加减层高于比较、低于乘除,同层按左结合嵌套。 left:(@) plus() right:@ { binary(left, BinaryOp::Add, right) } left:(@) minus() right:@ { binary(left, BinaryOp::Subtract, right) } -- + // 乘除层先构造节点,再交给外围的加减/比较层。 left:(@) star() right:@ { binary(left, BinaryOp::Multiply, right) } left:(@) slash() right:@ { binary(left, BinaryOp::Divide, right) } -- + // 前缀 await 构造等待节点,是否允许停放由执行上下文决定。 start:keyword("await") operand:@ { Expr { span: start.through(operand.span), kind: ExprKind::Await(Box::new(operand)) } } + // 前缀取负包住当前高优先级操作数,不在语法阶段计算数值。 start:minus() operand:@ { Expr { span: start.through(operand.span), kind: ExprKind::Unary { operation: UnaryOp::Negate, operand: Box::new(operand) } } } -- + // 后缀表达式位于最高优先级层,先形成调用/成员链,再被一元或二元节点包裹。 expression:postfix_expression() { expression } } + // 优先级最高的 primary 后接零或多个成员/调用后缀,从左到右 fold 保持链式求值结构。 rule postfix_expression() -> Expr = expression:primary() suffixes:suffix()* { + // parser::postfix 每次把先前结果作为接收对象或被调用表达式,构造 a.b(c).d 一类链。 suffixes.into_iter().fold(expression, postfix) } + // postfix_expression 共用后缀;点后取 Name,调用保存参数列表及右括号范围。 rule suffix() -> Suffix = dot() name:name() { Suffix::Member(name) } / lparen() arguments:(expression() ** comma()) end:rparen() { Suffix::Call(arguments, end) } + // 原子表达式:括号、符号、数字、字符串或名称;AST 文本均从匹配 Token 原文复制/解码。 rule primary() -> Expr = start:lparen() expression:expression() end:rparen() { + // 括号不新增语义节点种类,只把子表达式范围扩大到两侧括号。 Expr { span: start.through(end), ..expression } } / start:dot() name:name() { + // 点开头的符号与成员后缀不同;前端保留它,编译器当前尚不支持生成该运行时值。 Expr { span: start.through(name.span), kind: ExprKind::Symbol(name) } } / [Token { kind: TokenKind::Number, span }] { + // 数值保存原始十进制拼写,精确解析与负号处理留给 codegen。 Expr { span, kind: ExprKind::Number(source[span.start..span.end].into()) } } / [Token { kind: TokenKind::String, span }] { + // Token 跨度含引号,去掉引号后调用已验证转义的 decode_string,AST 保存解码结果。 Expr { span, kind: ExprKind::String(decode_string(&source[span.start + 1..span.end - 1])) } } / name:name() { Expr { span: name.span, kind: ExprKind::Name(name.text) } } diff --git a/crates/frontend/src/lexer.peg b/crates/frontend/src/lexer.peg index c3240f4..4234d2a 100644 --- a/crates/frontend/src/lexer.peg +++ b/crates/frontend/src/lexer.peg @@ -1,20 +1,28 @@ +// lexer.rs 编译时引入,生成 lexical::next;输入为本轮尚未消费的 UTF-8 后缀,所有 position! 都相对这个后缀。 peg::parser! { + // Lexer::next 调用的字符串扫描规则;每轮消费 trivia 后至多一个 Token。 grammar lexical() for str { + // next 首先跳过 ASCII 空白和双斜杠行注释,不产生 Token;注释在 CR/LF 前结束。 rule trivia() = ([' ' | '\t' | '\r' | '\n'] / "//" [^'\r' | '\n']*)* + // name 首字符允许下划线或 Unicode 字母,不允许数字开头。 rule name_start() = [character if character == '_' || character.is_alphabetic()] + // name 后续字符允许下划线或 Unicode 字母/数字;number 也用它识别非法黏连后缀。 rule name_continue() = [character if character == '_' || character.is_alphanumeric()] + // kind 的名称分支;关键字也返回 Name,具体语法上下文稍后才识别。 rule name() -> Result = name_start() name_continue()* { Ok(TokenKind::Name) } + // kind 的十进制数分支;无符号整数后可接点和至少一位小数,负号留给单独操作符。 rule number() -> Result = start:position!() ['0'..='9']+ ("." ['0'..='9']+)? suffix:$(name_continue()*) end:position!() { + // 数字后没有名称字符才是完整数 Token;否则将整段作为词法错误,避免误拆成两个合法 Token。 if suffix.is_empty() { Ok(TokenKind::Number) } else { @@ -25,32 +33,42 @@ peg::parser! { } } + // string 只接受换行、制表、双引号、反斜杠四种反斜杠转义,解码在 parser 的 decode_string 中进行。 rule escape() = "\\" ['n' | 't' | '"' | '\\'] + // string 的普通字符不能是引号、反斜杠、原始换行或 NUL;其他 Unicode 字符原样消费。 rule string_character() = [^'"' | '\\' | '\r' | '\n' | '\0'] + // string 主体停止后检查闭引号或报告具体错误;start 是开引号位置,供未闭合字符串定位。 rule string_end(start: usize) -> Result<(), Diagnostic> = "\"" { Ok(()) } + // 主体已消费所有合法转义,剩余反斜杠加字符必是未支持的转义。 / position:position!() "\\" [_] end:position!() { Err(Diagnostic::lexical(Span::new(position, end), "unsupported string escape")) } + // 反斜杠后直接 EOF,按从开引号到 EOF 的未闭合范围报告。 / "\\" end:position!() ![_] { Err(Diagnostic::lexical(Span::new(start, end), "unterminated string")) } + // 原始换行/NUL 不可出现在字符串体内,定位具体违规字符。 / position:position!() ['\r' | '\n' | '\0'] end:position!() { Err(Diagnostic::lexical(Span::new(position, end), "unescaped newline or NUL in string")) } + // 普通 EOF 也表示缺少闭引号,范围从开引号覆盖到输入结尾。 / end:position!() ![_] { Err(Diagnostic::lexical(Span::new(start, end), "unterminated string")) } + // kind 的字符串分支;扫描已验证主体及终结情况,Token 跨度仍包含引号并保持未解码原文。 rule string() -> Result = start:position!() "\"" (escape() / string_character())* end:string_end(start) { + // 闭引号验证通过才确认 String 种类,错误按值继续向上传递。 end.map(|()| TokenKind::String) } + // kind 的操作符/分隔符分支;共享前缀的双字符比较必须放在单字符比较/赋值之前。 rule punctuation() -> TokenKind = "(" { TokenKind::LeftParen } / ")" { TokenKind::RightParen } @@ -71,6 +89,7 @@ peg::parser! { / "*" { TokenKind::Star } / "/" { TokenKind::Slash } + // token 按顺序尝试名称、数字、字符串、标点;未知字符作为错误值返回,避免吞掉后重试别种 Token。 rule kind() -> Result = name() / number() @@ -80,6 +99,7 @@ peg::parser! { Err(Diagnostic::lexical(Span::new(start, end), "unexpected source character")) } + // next 用此包装单词种类及相对字节范围;只在 kind 成功时生成 Token。 rule token() -> Result = start:position!() kind:kind() end:position!() { kind.map(|kind| Token { kind, span: Span::new(start, end) }) @@ -87,9 +107,12 @@ peg::parser! { // Scan only one prefix. Errors are returned as values, so a matched // malformed number/string cannot backtrack into another token kind. + // 禁用默认整段消费检查,才能逐个前缀扫描;最终 EOF 由规则内的 ![_] 判断。 #[no_eof] + // Lexer::next 的唯一公开扫描入口;允许剩余后缀尚有内容,返回本轮消费字节数和 Token/EOF/诊断。 pub rule next() -> (usize, Result, Diagnostic>) = trivia() result:(token:token() { token.map(Some) } / ![_] { Ok(None) }) end:position!() { + // end 包括前导 trivia 与当前 Token;Lexer 用它推进游标,再把结果跨度加回本轮原文起点。 (end, result) } } diff --git a/crates/frontend/src/reader.rs b/crates/frontend/src/reader.rs index 60d2c54..48c41f2 100644 --- a/crates/frontend/src/reader.rs +++ b/crates/frontend/src/reader.rs @@ -1,3 +1,4 @@ +//! 宿主及前端测试在词法处理前调用的无状态读取适配器;统一验证 UTF-8,原样保留字节解码后的文本与来源。 use std::error::Error; use std::fmt; use std::fs::File; @@ -9,26 +10,34 @@ use crate::{SourceOrigin, SourceUnit}; /// The host resolves names, authorizes access and selects a revision before /// returning its source bytes. No URI path layout or hash algorithm is assumed. +/// 宿主实现的 endor URI 解析协议;Reader 不内置仓库、网络、路径布局或版本算法。 pub trait EndorSourceProvider { + /// EndorReader::from_endor 在基础 URI 检查后调用;宿主负责授权、定位版本并返回该版本原始字节。 fn load(&self, uri: &str) -> io::Result; } #[derive(Debug, Clone, PartialEq, Eq)] +/// provider.load 的拥有型结果;Reader 验证非空版本标签及 UTF-8 后移入 SourceUnit。 pub struct ResolvedEndorSource { + // 所选版本的原始标签,前端仅检查 trim 后非空,不规范化或验证哈希。 pub revision: String, + // 该版本的源码字节,尚未验证 UTF-8,也未进行换行或编码转换。 pub bytes: Vec, } /// Stateless source adapters, before lexing. Streams are read to EOF; session /// prompting and incomplete REPL input detection belong to the interactive host. #[derive(Debug, Clone, Copy, Default)] +/// 宿主/测试共用的读取门面;每次调用独立产生快照,流读到 EOF,不保存 REPL 会话状态。 pub struct EndorReader; impl EndorReader { + /// 宿主和编译测试提供现成 UTF-8 文本时调用;String 类型已保证编码有效,只包装来源与原文。 pub fn from_text(name: impl Into, text: impl Into) -> SourceUnit { SourceUnit::new(SourceOrigin::Text { name: name.into() }, text.into()) } + /// 宿主/测试把 Read 流读成一个快照;name 仅用于来源显示,读取失败保留流来源名。 pub fn from_stream( name: impl Into, reader: impl Read, @@ -36,11 +45,15 @@ impl EndorReader { Self::read(SourceOrigin::Stream { name: name.into() }, reader) } + /// 宿主/文件测试调用,先保存提供的路径身份再打开文件;打开/读取/解码错误均带相同来源。 pub fn from_file(path: impl AsRef) -> Result { + // 借用调用方传入路径用于打开;来源另存拥有副本,不要求先规范化或转为绝对路径。 let path = path.as_ref(); + // 在打开之前保存来源,连文件不存在或权限错误也能指明请求的路径。 let origin = SourceOrigin::File { path: path.to_owned(), }; + // 打开失败属于 IO 错误;成功后与流读取共用 read 和 decode。 let file = File::open(path).map_err(|error| SourceReadError { origin: origin.to_string(), kind: SourceReadErrorKind::Io(error), @@ -48,33 +61,39 @@ impl EndorReader { Self::read(origin, file) } + /// 宿主/URI 适配测试调用;前端做基础定位符检查,宿主 provider 选择版本,成功后才解码源码。 pub fn from_endor( uri: &str, provider: &P, ) -> Result { // Only recognize the scheme and reject empty/control-filled locators. // Namespace syntax, revision selectors and percent decoding are host policy. + // 协议大小写不敏感,冒号后不能为空,整串不能含空白/控制字符;其余语义交给宿主。 let valid = uri.split_once(':').is_some_and(|(scheme, address)| { scheme.eq_ignore_ascii_case("endor") && !address.is_empty() }) && !uri .chars() .any(|character| character.is_whitespace() || character.is_control()); + // 无效定位符先拒绝,不能调用 provider 触发不必要的外部访问。 if !valid { return Err(SourceReadError { origin: uri.into(), kind: SourceReadErrorKind::InvalidEndorUri, }); } + // 基础检查通过后委托宿主,宿主解析失败统一包装为带原 URI 的 IO 错误。 let resolved = provider.load(uri).map_err(|error| SourceReadError { origin: uri.into(), kind: SourceReadErrorKind::Io(error), })?; + // 没有解析后的版本身份就不能构成稳定来源;仅检查,不把原标签改成 trim 后内容。 if resolved.revision.trim().is_empty() { return Err(SourceReadError { origin: uri.into(), kind: SourceReadErrorKind::MissingRevision, }); } + // 保存原 URI 和 provider 返回的版本标签,诊断及生成代码文件名沿用它们。 let origin = SourceOrigin::Endor { uri: uri.into(), revision: resolved.revision, @@ -82,7 +101,9 @@ impl EndorReader { Self::decode(origin, resolved.bytes) } + /// from_stream/from_file 共用;完整读到 EOF 才尝试 UTF-8 解码,中途失败不返回部分源码快照。 fn read(origin: SourceOrigin, mut reader: impl Read) -> Result { + // 收集原始字节,不过滤 BOM、换行、空白或 NUL;编码和后续语法各按自己的阶段处理。 let mut bytes = Vec::new(); reader .read_to_end(&mut bytes) @@ -93,7 +114,9 @@ impl EndorReader { Self::decode(origin, bytes) } + /// read/from_endor 共用;只接受严格 UTF-8,不做有损替换,成功原样构造拥有快照。 fn decode(origin: SourceOrigin, bytes: Vec) -> Result { + // String::from_utf8 失败保留 FromUtf8Error,来源显示中包含文件/流/已解析版本身份。 let text = String::from_utf8(bytes).map_err(|error| SourceReadError { origin: origin.to_string(), kind: SourceReadErrorKind::InvalidUtf8(error), @@ -103,22 +126,32 @@ impl EndorReader { } #[derive(Debug)] +/// Reader 的失败分类,区别外部 IO、文本编码及 URI/版本协议问题,尚未进入词法阶段。 pub enum SourceReadErrorKind { + // 文件打开、流读取或 provider.load 返回的底层 IO 原因。 Io(io::Error), + // 严格 UTF-8 解码失败,保留有效字节前缀位置及原始错误对象。 InvalidUtf8(FromUtf8Error), + // Reader 的基础 endor URI 检查不通过,没有调用 provider。 InvalidEndorUri, + // provider 成功返回字节但未提供非空版本标签,不能创建已解析来源。 MissingRevision, } #[derive(Debug)] +/// Reader 公开返回给宿主的错误,拥有来源显示文本与具体原因,可沿标准 Error 链追查底层失败。 pub struct SourceReadError { + // 错误发生时固化的来源字符串,不依赖失败的输入流或 SourceUnit 仍存在。 pub origin: String, + // 分类及可能的底层错误对象,供宿主程序化判断。 pub kind: SourceReadErrorKind, } impl fmt::Display for SourceReadError { + /// 宿主显示错误时先输出来源,再按类别显示底层说明、无效 UTF-8 字节位置或协议错误。 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { write!(formatter, "{}: ", self.origin)?; + // IO/编码错误保留底层原因;URI 和缺版本是 Reader 自身协议错误。 match &self.kind { SourceReadErrorKind::Io(error) => write!(formatter, "{error}"), SourceReadErrorKind::InvalidUtf8(error) => write!( @@ -137,7 +170,9 @@ impl fmt::Display for SourceReadError { } impl Error for SourceReadError { + /// 标准错误链查询入口;IO 与编码错误返回原始原因,Reader 协议错误没有下层异常。 fn source(&self) -> Option<&(dyn Error + 'static)> { + // IO/编码错误保留底层原因;URI 和缺版本是 Reader 自身协议错误。 match &self.kind { SourceReadErrorKind::Io(error) => Some(error), SourceReadErrorKind::InvalidUtf8(error) => Some(error), -- 2.52.0 From ce0f507953eff044e3432f3cdba2bb3c4b32501e Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 18:46:57 +0800 Subject: [PATCH 43/62] docs(frontend): explain AST validation and lexical tests --- crates/frontend/src/ast/wire.rs | 35 +++++++++++++++++ crates/frontend/tests/ast.rs | 65 ++++++++++++++++++++++++++++++++ crates/frontend/tests/lexical.rs | 11 ++++++ 3 files changed, 111 insertions(+) diff --git a/crates/frontend/src/ast/wire.rs b/crates/frontend/src/ast/wire.rs index 8eaf471..3365381 100644 --- a/crates/frontend/src/ast/wire.rs +++ b/crates/frontend/src/ast/wire.rs @@ -1,3 +1,4 @@ +//! 宿主与 compiler::compile_ast 使用的版本化 AST 文档接口;校验结构、跨度和包含关系,不执行源码。 use std::fmt; use super::visitor::Visitor; @@ -5,21 +6,32 @@ use super::{AST_SCHEMA_ID, AstDocument, AstRoot}; use crate::Span; #[derive(Debug)] +/// JSON 编解码或文档验证的公开错误,供宿主、编译器包装及 AST 测试按类别判断。 pub enum AstError { + // JSON 语法、生成类型反序列化约束或序列化失败,保留 serde 原因。 Json(serde_json::Error), + // 文档 schema 身份与当前生成类型不一致,不能按此版本解释。 SchemaMismatch { + // 当前编译进前端的 schema 标识,来自生成代码的 AST_SCHEMA_ID。 expected: &'static str, + // 外部文档提供的拥有型标识,错误不依赖原输入缓冲存活。 found: String, }, + // 逆序、超出 JS 安全整数,或有源码校验时越界/切断 UTF-8 的节点范围。 InvalidSpan(Span), + // 子节点虽可能单独合法,但没有完全落在直接父节点范围内。 ChildOutsideParent { + // Visitor 祖先栈顶的直接父范围,用于诊断包含关系。 parent: Span, + // 当前试图进入、但越过父边界的子范围。 child: Span, }, } impl fmt::Display for AstError { + /// 宿主显示文档错误时调用,按类别保留 schema 或字节范围细节。 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // 根据具体错误分类输出信息或提供底层原因;schema/跨度错误由本模块产生。 match self { Self::Json(error) => write!(f, "invalid AST JSON: {error}"), Self::SchemaMismatch { expected, found } => { @@ -38,7 +50,9 @@ impl fmt::Display for AstError { } impl std::error::Error for AstError { + /// 标准 Error 链查询入口;只有 serde JSON 失败有外部底层错误对象。 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + // 根据具体错误分类输出信息或提供底层原因;schema/跨度错误由本模块产生。 match self { Self::Json(error) => Some(error), _ => None, @@ -47,13 +61,16 @@ impl std::error::Error for AstError { } impl From for AstError { + /// from_json/to_json 使用 ? 时,把 serde 错误统一转换为 AstError::Json。 fn from(error: serde_json::Error) -> Self { Self::Json(error) } } impl AstDocument { + /// compiler::compile_ast、宿主及测试为已有根创建文档;附当前 schema 并校验结构,尚无原文可查边界。 pub fn new(root: AstRoot) -> Result { + // 按当前 schema 包装拥有根,再校验,失败不把无效文档作为成功结果返回。 let document = Self { schema: AST_SCHEMA_ID.into(), root, @@ -63,35 +80,45 @@ impl AstDocument { } /// Decode a versioned syntax tree. This does not compile or execute it. + /// 宿主/编辑器或测试输入 JSON 时调用;先解码类型结构,再检查 schema 身份及节点跨度。 pub fn from_json(text: &str) -> Result { + // 生成类型上的 serde 约束会在此拒绝未知字段或错误形状,再由 validate 检查文档级规则。 let document: Self = serde_json::from_str(text)?; document.validate()?; Ok(document) } + /// 宿主/编辑器传输及往返测试调用;先验证当前文档,避免序列化已被手工改坏的结构。 pub fn to_json(&self) -> Result { self.validate()?; Ok(serde_json::to_string(self)?) } /// Check schema identity, ranges, and parent/child containment. + /// new/from_json/to_json 共用的无原文验证:范围顺序、JS 安全整数及父子包含;不推断文本长度。 pub fn validate(&self) -> Result<(), AstError> { self.validate_with_source(None) } /// Additionally check bounds and UTF-8 boundaries against original text. /// This does not check that the text and AST have identical semantics. + /// compiler::compile_ast 和源码范围测试调用;额外用对应原文检查字节边界及长度,不证明 AST 语义与文本相同。 pub fn validate_source(&self, source: &str) -> Result<(), AstError> { self.validate_with_source(Some(source)) } + /// validate/validate_source 共用实现,source 的有无决定是否检查真实 UTF-8 原文范围。 fn validate_with_source(&self, source: Option<&str>) -> Result<(), AstError> { + // 先核对文档版本,避免用当前访问器接受另一结构版本的数据。 if self.schema != AST_SCHEMA_ID { return Err(AstError::SchemaMismatch { + // 当前编译进前端的 schema 标识,来自生成代码的 AST_SCHEMA_ID。 expected: AST_SCHEMA_ID, + // 外部文档提供的拥有型标识,错误不依赖原输入缓冲存活。 found: self.schema.clone(), }); } + // 每次验证使用新祖先栈,从根递归,失败后这份临时状态随调用结束释放。 SpanValidator { source, parents: Vec::new(), @@ -100,16 +127,21 @@ impl AstDocument { } } +/// AstDocument 校验使用的借用节点访问器;source 可选,parents 跟踪当前递归路径的节点范围。 struct SpanValidator<'a> { + // 有原文时校验 slice;无原文时只做结构与可表示范围检查。 source: Option<&'a str>, + // 成功进入才压栈,离开时弹栈,确保每个子节点与直接父节点比较。 parents: Vec, } impl Visitor for SpanValidator<'_> { type Error = AstError; + /// 默认 Visitor 在进入各语法节点前调用;只有检查通过才登记本节点,失败不需要为当前节点弹栈。 fn enter_node(&mut self, span: Span) -> Result<(), AstError> { // JSON offsets must also be exactly representable by editor JS numbers. + // end 不超过 JS Number 的最大安全整数;顺序合法同时保证 start 不会比 end 更大。 if span.start > span.end || span.end as u128 > 9_007_199_254_740_991 || self @@ -118,6 +150,7 @@ impl Visitor for SpanValidator<'_> { { return Err(AstError::InvalidSpan(span)); } + // 根没有父范围;其余节点不能越过直接父节点的任一端点,允许范围相等。 if let Some(parent) = self.parents.last().copied() && (span.start < parent.start || span.end > parent.end) { @@ -126,10 +159,12 @@ impl Visitor for SpanValidator<'_> { child: span, }); } + // 当前节点全部合法后才成为后续子节点的父范围。 self.parents.push(span); Ok(()) } + /// 默认 Visitor 在已进入节点的遍历结束后调用,即使子遍历失败,也会为成功进入的祖先执行退出。 fn leave_node(&mut self) { self.parents.pop(); } diff --git a/crates/frontend/tests/ast.rs b/crates/frontend/tests/ast.rs index 816e2e9..7d2ce89 100644 --- a/crates/frontend/tests/ast.rs +++ b/crates/frontend/tests/ast.rs @@ -1,3 +1,4 @@ +//! Rust 测试框架验证语法根、生成 Visitor、原文定位和版本化 JSON 接口,不执行编译后代码。 use frontend::ast::visitor::{Visitor, walk_expr, walk_function}; use frontend::ast::{ AST_SCHEMA_ID, AstDocument, AstError, AstRoot, Expr, ExprKind, Function, Spanned, StmtKind, @@ -5,15 +6,19 @@ use frontend::ast::{ use frontend::{EndorReader, ParseMode, Span, parse_ast, parse_source_ast}; #[test] +/// 由 Rust 测试框架调用:块内允许多个调用表达式语句及无 return 空体,但显式 return 必须位于块末尾。 fn function_bodies_accept_method_statements_and_optional_trailing_return() { + // 仅解析指定模式的源码,取得拥有型 AstRoot,不分配 VM 对象。 let root = parse_ast( "function click(): Unit { title.setText(\"changed\") card.setColor(\"#FFEEDD\") }", ParseMode::Module, ) .unwrap(); + // 模块模式必须产出 Program,不能把声明错误地包装成表达式或交互根。 let AstRoot::Module(program) = root else { panic!("expected module") }; + // 借用首个顶层函数,用于检查块内语句分类及顺序。 let function = program.functions().next().unwrap(); assert_eq!(function.body.statements.len(), 2); assert!( @@ -34,18 +39,25 @@ fn function_bodies_accept_method_statements_and_optional_trailing_return() { } #[test] +/// 由 Rust 测试框架调用:核对模块/表达式/单条交互的根类型与语句种类,文档范围有效,来源诊断保留 Unicode 行列。 fn parse_modes_produce_distinct_roots_and_preserve_source_diagnostics() { + // 保留与 AST 跨度相对应的原始 UTF-8 文本,后续不规范化空白或换行。 let source = "function echo(value: String): String { let copy = value return copy }"; + // 仅解析指定模式的源码,取得拥有型 AstRoot,不分配 VM 对象。 let root = parse_ast(source, ParseMode::Module).unwrap(); + // 模块模式必须产出 Program,不能把声明错误地包装成表达式或交互根。 let AstRoot::Module(program) = &root else { panic!("expected module") }; + // 借用首个顶层函数,用于检查块内语句分类及顺序。 let function = program.functions().next().unwrap(); assert_eq!(function.body.statements.len(), 2); + // 第一条是 let Binding,不能与后面的 Return 混成同一语句种类。 assert!(matches!( function.body.statements[0].kind, StmtKind::Binding(_) )); + // 第二条明确为末尾 Return,顺序应与源码一致。 assert!(matches!( function.body.statements[1].kind, StmtKind::Return(_) @@ -55,10 +67,15 @@ fn parse_modes_produce_distinct_roots_and_preserve_source_diagnostics() { .validate_source(source) .unwrap(); + // 仅解析指定模式的源码,取得拥有型 AstRoot,不分配 VM 对象。 let root = parse_ast("echo(\"日\")", ParseMode::Expression).unwrap(); + // Expression 模式只能返回表达式根,不带交互语句包装。 assert!(matches!(root, AstRoot::Expression(_))); + // 一条 let、一次调用和一个函数声明分别构成交互提交,均应只含一个语句。 for text in ["let value = \"日\"", "echo(value)", source] { + // 仅解析指定模式的源码,取得拥有型 AstRoot,不分配 VM 对象。 let root = parse_ast(text, ParseMode::Interactive).unwrap(); + // 解构交互根并借用其单条语句,避免只检查解析成功而忽略根形状。 let AstRoot::Interactive(input) = &root else { panic!("expected interactive") }; @@ -70,7 +87,9 @@ fn parse_modes_produce_distinct_roots_and_preserve_source_diagnostics() { } assert!(parse_ast("let x = 1 let y = 2", ParseMode::Interactive).is_err()); assert!(parse_ast("", ParseMode::Interactive).is_err()); + // 给语法错误附会话来源身份,字符串中的日字使字节偏移与 Unicode 列不同。 let source = EndorReader::from_text("", "\"日\" + )"); + // 故意错误的快照解析必须返回带来源诊断,之后核对实际显示行列。 let error = parse_source_ast(&source, ParseMode::Expression).unwrap_err(); assert_eq!(error.location.line, 1); assert_eq!(error.location.column, 7); @@ -78,29 +97,41 @@ fn parse_modes_produce_distinct_roots_and_preserve_source_diagnostics() { } #[test] +/// 由 Rust 测试框架调用:自定义访问器收集函数名与引用名,并验证子遍历提前失败后,成功进入的祖先节点退出钩子仍全部配平。 fn generated_visitors_reach_names_and_unwind_node_hooks_on_error() { #[derive(Default)] + /// 本测试的可变访问器状态;节点只借用,收集文本拥有副本,depth 跟踪默认节点钩子的配平。 struct Names { + // 自定义 visit_function 记录声明名,和表达式引用名分开。 functions: Vec, + // visit_expr 仅收集 ExprKind::Name,不包含类型注解或属性名。 references: Vec, + // enter/leave 计数,遍历结束或提前失败后都应回到零。 depth: usize, + // 为 true 时在第一个引用名处主动失败,用来检查错误回溯退出路径。 stop: bool, } impl Visitor for Names { type Error = &'static str; + /// 生成的默认 visit 方法成功进入节点时调用,记录当前已进入祖先数量。 fn enter_node(&mut self, _span: Span) -> Result<(), Self::Error> { self.depth += 1; Ok(()) } + /// 默认 visit 在子遍历完成或失败后调用,撤销此前成功进入的节点计数。 fn leave_node(&mut self) { self.depth -= 1; } + /// 自定义函数访问入口先记录名称,再显式 walk_function 保持所有字段的递归访问。 fn visit_function(&mut self, node: &Function) -> Result<(), Self::Error> { self.functions.push(node.name.text.clone()); walk_function(self, node) } + /// 自定义表达式访问入口只特殊处理引用名;其余结构继续由 walk_expr 分派。 fn visit_expr(&mut self, node: &Expr) -> Result<(), Self::Error> { + // 仅变量引用表达式进入收集/主动失败分支,字面量等节点仍照常递归。 if let ExprKind::Name(name) = &node.kind { + // 在调用 walk_expr 之前失败;默认祖先 visit 必须仍执行各自的 leave_node。 if self.stop { return Err("stopped at reference"); } @@ -109,16 +140,19 @@ fn generated_visitors_reach_names_and_unwind_node_hooks_on_error() { walk_expr(self, node) } } + // 仅解析指定模式的源码,取得拥有型 AstRoot,不分配 VM 对象。 let root = parse_ast( "function echo(value: String): String { let copy = value return copy }", ParseMode::Module, ) .unwrap(); + // 正常遍历从空状态开始,检查名称收集顺序及最终深度。 let mut names = Names::default(); names.visit_ast_root(&root).unwrap(); assert_eq!(names.functions, ["echo"]); assert_eq!(names.references, ["value", "copy"]); assert_eq!(names.depth, 0); + // 第二份访问器主动终止在引用名,不能复用正常访问器掩盖深度泄漏。 let mut stopped = Names { stop: true, ..Names::default() @@ -128,10 +162,14 @@ fn generated_visitors_reach_names_and_unwind_node_hooks_on_error() { } #[test] +/// 由 Rust 测试框架调用:括号范围对应原文,CRLF 按一行、Unicode 按一列;安全定位拒绝逆序、越界及切断字符的范围。 fn spans_share_unicode_locations_and_safe_original_source_access() { + // 保留与 AST 跨度相对应的原始 UTF-8 文本,后续不规范化空白或换行。 let source = " \r\n(値)"; + // 仅解析指定模式的源码,取得拥有型 AstRoot,不分配 VM 对象。 let root = parse_ast(source, ParseMode::Expression).unwrap(); assert_eq!(root.source_text(source), Some("(値)")); + // 用同一原文把根节点的字节范围转换成一基行列,CRLF 只增加一行。 let location = root.location(source).unwrap(); assert_eq!((location.line, location.column), (2, 1)); assert_eq!(Span::new(1, 3).slice("値"), None); @@ -140,7 +178,9 @@ fn spans_share_unicode_locations_and_safe_original_source_access() { } #[test] +/// 由 Rust 测试框架调用:三个语法根 JSON 往返不丢文本/数值拼写,文档 schema 与生成的 JSON Schema/TypeScript 标识一致。 fn versioned_json_roundtrips_all_roots_and_preserves_number_spelling() { + // 分别覆盖含长十进制的模块、含符号/转义字符串的表达式和交互绑定。 for (source, mode) in [ ( "function f(): Number { return 00012345678901234567890.00100 }", @@ -149,16 +189,21 @@ fn versioned_json_roundtrips_all_roots_and_preserves_number_spelling() { ("f(.名前, \"日\\n\")", ParseMode::Expression), ("let value = 1", ParseMode::Interactive), ] { + // 用当前 schema 包装合法解析树并验证结构,为后续往返或定点破坏提供基线。 let document = AstDocument::new(parse_ast(source, mode).unwrap()).unwrap(); + // 序列化前接口再次验证文档;输出应保留 schema、节点种类和原文字节范围。 let json = document.to_json().unwrap(); + // 解码后重新验证文档结构,结果应与原文档完整相等。 let restored = AstDocument::from_json(&json).unwrap(); assert_eq!(restored, document); restored.validate_source(source).unwrap(); assert!(json.contains(AST_SCHEMA_ID)); + // 模块案例包含带前导零和尾零的长数值,JSON 必须保留原拼写而非转浮点数。 if mode == ParseMode::Module { assert!(json.contains("00012345678901234567890.00100")); } } + // 读取仓库已生成的 JSON Schema,只检查标识与常量,不在测试中重新生成文件。 let schema: serde_json::Value = serde_json::from_str(include_str!("../editor/ast.schema.json")).unwrap(); assert_eq!(schema["$id"], AST_SCHEMA_ID); @@ -170,35 +215,45 @@ fn versioned_json_roundtrips_all_roots_and_preserves_number_spelling() { } #[test] +/// 由 Rust 测试框架调用:分别拒绝错误 schema、未知字段、子节点越界、逆序范围以及对原文切断 UTF-8 字符的范围。 fn json_interfaces_reject_unknown_fields_schema_changes_and_invalid_ranges() { + // 保留与 AST 跨度相对应的原始 UTF-8 文本,后续不规范化空白或换行。 let source = "(f(値))"; + // 用当前 schema 包装合法解析树并验证结构,为后续往返或定点破坏提供基线。 let document = AstDocument::new(parse_ast(source, ParseMode::Expression).unwrap()).unwrap(); + // 用可编辑 JSON 值破坏一个属性;每个错误案例之后重新从合法 document 创建。 let mut value = serde_json::to_value(&document).unwrap(); + // 首先只破坏版本身份,应在 schema 比较阶段报告 SchemaMismatch。 value["schema"] = "urn:endor:ast:unknown".into(); assert!(matches!( AstDocument::from_json(&value.to_string()), Err(AstError::SchemaMismatch { .. }) )); value = serde_json::to_value(&document).unwrap(); + // 恢复合法 JSON 后增加未知字段,生成类型的 deny_unknown_fields 应在解码阶段拒绝。 value["root"]["data"]["unexpected"] = true.into(); assert!(matches!( AstDocument::from_json(&value.to_string()), Err(AstError::Json(_)) )); value = serde_json::to_value(&document).unwrap(); + // 再次恢复后缩小父范围,子节点仍在原位置,应报告 ChildOutsideParent。 value["root"]["data"]["span"]["end"] = 1.into(); assert!(matches!( AstDocument::from_json(&value.to_string()), Err(AstError::ChildOutsideParent { .. }) )); + // 取出合法表达式后直接破坏其范围,验证 new 也拒绝无效手工构造树。 let AstRoot::Expression(mut expression) = document.root else { unreachable!() }; + // 无需原文也能识别 end 小于 start 的逆序范围。 expression.span = Span::new(5, 4); assert!(matches!( AstDocument::new(AstRoot::Expression(expression)), Err(AstError::InvalidSpan(_)) )); + // 无原文时范围本身合法,但放回三字节字符原文时起点切断 UTF-8,应由 validate_source 拒绝。 let root = AstRoot::Expression(Expr { kind: ExprKind::Name("値".into()), span: Span::new(1, 3), @@ -210,27 +265,37 @@ fn json_interfaces_reject_unknown_fields_schema_changes_and_invalid_ranges() { } #[test] +/// 由 Rust 测试框架调用:普通赋值使用独立 AST 节点并保留 Unicode 原文;链式赋值和非成员调用目标赋值语法失败。 fn bare_assignment_has_its_own_ast_and_unicode_source_ranges() { + // 保留与 AST 跨度相对应的原始 UTF-8 文本,后续不规范化空白或换行。 let source = "标题 = ui.text(\"你好\")"; + // 仅解析指定模式的源码,取得拥有型 AstRoot,不分配 VM 对象。 let root = parse_ast(source, ParseMode::Interactive).unwrap(); + // 解构交互根并借用其单条语句,避免只检查解析成功而忽略根形状。 let AstRoot::Interactive(input) = &root else { panic!("interactive input") }; + // 普通赋值不是 let Binding,也不是成员赋值;名称节点和整句范围都要保持原文。 let StmtKind::Assignment(assignment) = &input.statements[0].kind else { panic!("assignment") }; assert_eq!(assignment.name.text, "标题"); assert_eq!(assignment.name.source_text(source), Some("标题")); assert_eq!(assignment.source_text(source), Some(source)); + // 用当前 schema 包装合法解析树并验证结构,为后续往返或定点破坏提供基线。 let document = AstDocument::new(root).unwrap(); document.validate_source(source).unwrap(); + // 序列化前接口再次验证文档;输出应保留 schema、节点种类和原文字节范围。 let json = document.to_json().unwrap(); assert_eq!(AstDocument::from_json(&json).unwrap(), document); assert!(parse_ast("value = 1 = 2", ParseMode::Interactive).is_err()); + // 单独解析属性写入,确保普通赋值的新增路径没有抢走 MemberAssignment。 let member = parse_ast("object.field = 1", ParseMode::Interactive).unwrap(); + // 必须检查节点类别,不能只根据字符串包含点就假定走了成员赋值规则。 let AstRoot::Interactive(member) = member else { panic!("interactive") }; + // 属性写入使用 MemberAssignment,不能退化为普通目标名赋值。 assert!(matches!( &member.statements[0].kind, StmtKind::MemberAssignment(_) diff --git a/crates/frontend/tests/lexical.rs b/crates/frontend/tests/lexical.rs index 0d5fd7d..4a48083 100644 --- a/crates/frontend/tests/lexical.rs +++ b/crates/frontend/tests/lexical.rs @@ -1,8 +1,12 @@ +//! Rust 测试框架直接验证 lex 的原文偏移与 Token 边界,不进入语法分析、编译或 VM。 use frontend::{DiagnosticKind, Span, TokenKind, lex}; #[test] +/// 验证多 Token 和 Unicode 前缀之后的错误仍定位原文绝对字节范围,并区分数字/转义/换行/EOF/未知字符失败。 fn lexical_errors_keep_original_byte_ranges_after_multiple_tokens() { + // 固定有效前缀含多字节名称,防止只在零偏移或 ASCII 输入上通过的错误位置实现。 let prefix = "let 商品 = "; + // 各尾部给出违规片段及准确错误说明,包含 Unicode 转义、原始 CR/LF/NUL 与两种未闭合结尾。 for (tail, offending, message) in [ ( "12.50名前 ;", @@ -19,8 +23,11 @@ fn lexical_errors_keep_original_byte_ranges_after_multiple_tokens() { ("\"未完\\", "\"未完\\", "unterminated string"), ("🌸 ;", "🌸", "unexpected source character"), ] { + // 把合法前缀与错误尾部拼成真实输入,后续位置必须指向完整原文。 let source = format!("{prefix}{tail}"); + // 分词必须失败,不允许回溯把非法数字/字符串拆成若干合法 Token。 let error = lex(&source).unwrap_err(); + // 从完整原文查找预期片段,用字节长度构造期望半开范围。 let start = source.find(offending).unwrap(); assert_eq!(error.kind, DiagnosticKind::Lexical, "{source:?}"); assert_eq!(error.span, Span::new(start, start + offending.len())); @@ -30,9 +37,13 @@ fn lexical_errors_keep_original_byte_ranges_after_multiple_tokens() { } #[test] +/// 验证注释与字符串中的双斜杠、成员点、除号和小数边界互不混淆,空白/注释输入不产生 Token。 fn token_boundaries_distinguish_members_division_comments_and_string_contents() { + // 原文混合 CRLF、Unicode、行注释中的 NUL,以及字符串内必须保留的双斜杠。 let source = " \t// 前置\r\n商品.価格 / 12.50// 行末\0\n\"//実文\" \r\n// 終端"; + // lex 跳过 trivia 后只保留语法 Token,所有跨度仍指向未经修改的 source。 let tokens = lex(source).unwrap(); + // 把种类与原文切片配对,既检查 Token 分类,也检查是否误吞相邻内容。 let spellings: Vec<_> = tokens .iter() .map(|token| (token.kind, &source[token.span.start..token.span.end])) -- 2.52.0 From e0e13ce6fcf416714035c9237a5716581d856afe Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 18:51:20 +0800 Subject: [PATCH 44/62] docs(frontend): explain reader and syntax regression scenarios --- crates/frontend/examples/basic.endor | 6 +++ crates/frontend/tests/reader.rs | 68 ++++++++++++++++++++++++++++ crates/frontend/tests/syntax.rs | 66 +++++++++++++++++++++++++++ 3 files changed, 140 insertions(+) diff --git a/crates/frontend/examples/basic.endor b/crates/frontend/examples/basic.endor index 33e9a36..78345c7 100644 --- a/crates/frontend/examples/basic.endor +++ b/crates/frontend/examples/basic.endor @@ -1,9 +1,15 @@ +// frontend/tests/syntax.rs 解析此模块并检查两个声明;这里只演示 AST 输入,不自动调用函数或执行数值运算。 // Parsing produces an AST; it does not execute these functions. +// 供语法测试解析的恒等函数,显式 String 参数与返回注解均保存在 AST 中。 function echo(text: String): String { + // let 绑定把参数读取存为独立 Binding 节点,供前端检查语句结构。 let result = text + // 末尾 return 引用 result,解析后保留名称及原文范围。 return result } +// 供语法测试解析的双参数函数;参数和返回类型名仅作为注解文本,不在前端解析其身份。 function discountedPrice(price: Number, discount: Number): Number { + // 减法在 AST 中是包含两个名称读取的 Binary 节点,此示例解析过程不计算结果。 return price - discount } diff --git a/crates/frontend/tests/reader.rs b/crates/frontend/tests/reader.rs index b9729ee..3850cf8 100644 --- a/crates/frontend/tests/reader.rs +++ b/crates/frontend/tests/reader.rs @@ -1,3 +1,4 @@ +//! Rust 测试框架验证 Reader 适配器:完整 UTF-8 快照、来源/版本身份、错误阶段和宿主 URI 授权边界。 use std::cell::Cell; use std::error::Error; use std::io::{self, Cursor, Read}; @@ -9,19 +10,26 @@ use frontend::{ SourceReadErrorKind, lex_source, parse_source, parse_source_expression, }; +/// 流测试包装器,让 read_to_end 每次最多取得一个字节,强制 UTF-8 字符跨多次底层读取。 struct OneByte(R); impl Read for OneByte { + /// Reader 的 read_to_end 调用此适配器,把一次底层读取限制为至多一个字节。 fn read(&mut self, buffer: &mut [u8]) -> io::Result { + // 空输出缓冲保持零长度,其余读取都限制为一个字节,不能按单次读取独立解码。 let count = buffer.len().min(1); self.0.read(&mut buffer[..count]) } } #[test] +/// 由 Rust 测试框架调用:文本与单字节流保留相同 CRLF/Unicode 原文并解析成相同 AST/Token;空流可读取但空模块无效,BOM 保留到词法阶段拒绝。 fn strings_and_split_utf8_streams_produce_the_same_ast_and_exact_text() { + // 保留注释、CRLF、日文和四字节 emoji,使原文精确性及跨读取解码同时受检。 let text = "// 日本語\r\nfunction 表示(): String { return \"こんにちは🌸\" }\r\n"; + // String 文本已是 UTF-8,Reader 只包装拥有快照和显示名。 let memory = EndorReader::from_text("", text); + // 同一字节序列经 OneByte 拆分读取,Reader 应先完整聚合再验证 UTF-8。 let stream = EndorReader::from_stream("", OneByte(Cursor::new(text.as_bytes()))).unwrap(); assert_eq!(memory.text(), text); assert_eq!(stream.text(), text); @@ -42,22 +50,28 @@ fn strings_and_split_utf8_streams_produce_the_same_ast_and_exact_text() { } ); assert_eq!(lex_source(&memory).unwrap(), lex_source(&stream).unwrap()); + // 空字节流是合法 UTF-8 快照,是否为有效模块由后续语法阶段决定。 let empty = EndorReader::from_stream("", Cursor::new(b"")).unwrap(); assert!(empty.text().is_empty()); assert_eq!( parse_source(&empty).unwrap_err().diagnostic.kind, DiagnosticKind::Syntax ); + // Reader 不剥除 BOM,词法器看到原始开头字符后才决定是否接受。 let bom = EndorReader::from_stream("", Cursor::new("\u{feff}1".as_bytes())).unwrap(); assert_eq!(bom.text(), "\u{feff}1"); + // 当前用例要求此阶段失败,随后检查具体类别、来源或底层原因,避免仅接受任意错误。 let error = lex_source(&bom).unwrap_err(); assert_eq!(error.diagnostic.kind, DiagnosticKind::Lexical); assert_eq!(error.diagnostic.span.end, 3); } #[test] +/// 由 Rust 测试框架调用:语法与词法错误保留原始字节跨度、会话名及 Unicode 行列,源快照销毁后诊断仍可显示并提供错误链。 fn source_diagnostics_keep_session_names_unicode_locations_and_original_offsets() { + // 建立带来源名的原文快照,后续错误必须定位这份文本而非临时转换后的内容。 let source = EndorReader::from_text("", "\"日\" + )"); + // 当前用例要求此阶段失败,随后检查具体类别、来源或底层原因,避免仅接受任意错误。 let error = parse_source_expression(&source).unwrap_err(); assert_eq!(error.location.line, 1); assert_eq!(error.location.column, 7); @@ -66,11 +80,14 @@ fn source_diagnostics_keep_session_names_unicode_locations_and_original_offsets( ")" ); assert_eq!(error.origin, source.origin().clone()); + // 丢弃文本快照后仍应能显示已固化的来源和行列,不依赖借用的源码缓冲。 drop(source); assert!(error.to_string().starts_with(":1:7:")); assert!(error.source().is_some()); + // 建立带来源名的原文快照,后续错误必须定位这份文本而非临时转换后的内容。 let source = EndorReader::from_text("", "\r\n\"日\\q\""); + // 当前用例要求此阶段失败,随后检查具体类别、来源或底层原因,避免仅接受任意错误。 let error = lex_source(&source).unwrap_err(); assert_eq!(error.diagnostic.kind, DiagnosticKind::Lexical); assert_eq!(error.location.line, 2); @@ -79,8 +96,11 @@ fn source_diagnostics_keep_session_names_unicode_locations_and_original_offsets( } #[test] +/// 由 Rust 测试框架调用:非法字节、截断多字节字符及合法前缀后非法字节都在读取解码阶段拒绝,不作有损替换。 fn incomplete_or_invalid_utf8_is_rejected_without_replacement_characters() { + // 覆盖无效起始字节、不完整 UTF-8 序列,以及一个合法 ASCII 前缀后的错误。 for bytes in [vec![0xff], vec![0xe6, 0x97], vec![b'a', 0xff]] { + // 当前用例要求此阶段失败,随后检查具体类别、来源或底层原因,避免仅接受任意错误。 let error = EndorReader::from_stream("", OneByte(Cursor::new(bytes))).unwrap_err(); assert!(matches!(error.kind, SourceReadErrorKind::InvalidUtf8(_))); assert!(error.to_string().contains(": invalid UTF-8 at byte")); @@ -88,13 +108,17 @@ fn incomplete_or_invalid_utf8_is_rejected_without_replacement_characters() { } } +/// 模拟中途失败的流;布尔值记录是否已返回过第一个合法字节。 struct FailingStream(bool); impl Read for FailingStream { + /// Reader 的 read_to_end 调用;先成功一次再失败,检验中途错误不会变成部分成功。 fn read(&mut self, buffer: &mut [u8]) -> io::Result { + // 空缓冲不消耗模拟状态,符合 Read 的零长度读取约定。 if buffer.is_empty() { return Ok(0); } + // 第一次非空读取给出 x,后续非空读取才返回 IO 错误。 if !self.0 { self.0 = true; buffer[0] = b'x'; @@ -105,18 +129,24 @@ impl Read for FailingStream { } #[test] +/// 由 Rust 测试框架调用:读到一个有效字节后发生 IO 失败时仍整体失败,不向解析器提供半份快照。 fn a_failed_stream_never_returns_partially_read_source() { + // 当前用例要求此阶段失败,随后检查具体类别、来源或底层原因,避免仅接受任意错误。 let error = EndorReader::from_stream("", FailingStream(false)).unwrap_err(); assert!(matches!(error.kind, SourceReadErrorKind::Io(_))); assert!(error.to_string().contains("stream failure")); } +// 并行文件测试使用进程内递增编号避免重名;这里只需要原子唯一性,不发布共享数据。 static NEXT_FILE: AtomicUsize = AtomicUsize::new(0); +/// 本文件测试用的临时文件拥有者;构造写入字节,Drop 尽力删除,不留测试文件。 struct SourceFile(PathBuf); impl SourceFile { + /// 文件测试创建临时输入;路径组合进程 ID 和原子编号,写入失败直接使测试失败。 fn new(bytes: &[u8]) -> Self { + // 放在系统临时目录,不依赖当前仓库布局,同一进程的不同测试实例用不同编号。 let path = std::env::temp_dir().join(format!( "aster-reader-{}-{}.endor", std::process::id(), @@ -128,14 +158,18 @@ impl SourceFile { } impl Drop for SourceFile { + /// 退出文件测试作用域时清理文件,忽略已不存在等删除错误,避免 Drop 再次失败。 fn drop(&mut self) { let _ = std::fs::remove_file(&self.0); } } #[test] +/// 由 Rust 测试框架调用:文件保留原路径及 CRLF,坏编码与文件不存在分别产生读取编码错误和 NotFound IO 错误。 fn file_sources_preserve_the_path_and_read_errors_are_not_lexical_errors() { + // 建立合法模块临时文件,用作成功读取及路径来源的基线。 let file = SourceFile::new(b"function f(): Number { return 1 }\r\n"); + // 建立带来源名的原文快照,后续错误必须定位这份文本而非临时转换后的内容。 let source = EndorReader::from_file(&file.0).unwrap(); assert_eq!( source.origin(), @@ -145,25 +179,34 @@ fn file_sources_preserve_the_path_and_read_errors_are_not_lexical_errors() { ); assert!(source.text().ends_with("\r\n")); assert!(parse_source(&source).is_ok()); + // 单字节 0xff 不是 UTF-8,打开文件成功后应在解码阶段拒绝。 let invalid = SourceFile::new(&[0xff]); + // 当前用例要求此阶段失败,随后检查具体类别、来源或底层原因,避免仅接受任意错误。 let error = EndorReader::from_file(&invalid.0).unwrap_err(); assert!(matches!(error.kind, SourceReadErrorKind::InvalidUtf8(_))); assert!(error.to_string().contains(&invalid.0.display().to_string())); + // 从基线路径换一个未创建的扩展名,验证文件不存在作为 IO 错误暴露。 let missing = file.0.with_extension("missing"); + // 当前用例要求此阶段失败,随后检查具体类别、来源或底层原因,避免仅接受任意错误。 let error = EndorReader::from_file(&missing).unwrap_err(); + // 必须能解构出原始 IO 错误,之后检查 NotFound 或 PermissionDenied 的具体种类。 let SourceReadErrorKind::Io(error) = error.kind else { panic!("expected I/O error"); }; assert_eq!(error.kind(), io::ErrorKind::NotFound); } +/// URI 测试的可计数宿主,每次 load 返回不同版本和源码,用来检测隐式重复解析。 struct Provider { + // &self 的 load 通过 Cell 累计调用次数,不要求多线程共享。 calls: Cell, } impl EndorSourceProvider for Provider { + /// from_endor 调用,检查定位符并累计解析次数,返回新版本拥有字节。 fn load(&self, uri: &str) -> io::Result { assert_eq!(uri, "endor:workspace/namespace/function"); + // 每次解析选择新的版本编号,同时反映在标签与源码返回值中。 let version = self.calls.get() + 1; self.calls.set(version); Ok(ResolvedEndorSource { @@ -174,12 +217,17 @@ impl EndorSourceProvider for Provider { } #[test] +/// 由 Rust 测试框架调用:同一 URI 每次读取只调用一次宿主,后续解析不再解析 URI,旧快照不被新版本改写。 fn each_uri_read_resolves_once_and_old_snapshots_keep_their_original_revision() { + // 为本场景新建计数宿主,不与其他 URI 测试共享解析次数。 let provider = Provider { calls: Cell::new(0), }; + // URI 作为宿主可解释的定位符,Reader 不把 namespace/function 猜成磁盘路径。 let uri = "endor:workspace/namespace/function"; + // 第一份快照保存第一次解析的原文及 revision-1。 let first = EndorReader::from_endor(uri, &provider).unwrap(); + // 同一 URI 再读取得第二份独立快照,不能回写第一份文本或来源。 let second = EndorReader::from_endor(uri, &provider).unwrap(); assert_eq!(provider.calls.get(), 2); assert!(first.text().contains("return 1")); @@ -203,12 +251,16 @@ fn each_uri_read_resolves_once_and_old_snapshots_keep_their_original_revision() assert_eq!(provider.calls.get(), 2); // Parsing never resolves the URI again. } +/// 错误测试的固定宿主,按指定版本标签和字节返回,Reader 仍须自行校验协议及编码。 struct StaticProvider { + // 借用测试静态版本标签,load 时复制为拥有字符串。 revision: &'static str, + // 借用测试静态字节,load 时复制给 Reader 解码。 bytes: &'static [u8], } impl EndorSourceProvider for StaticProvider { + /// from_endor 调用,把测试指定标签与字节复制到结果,不提前替 Reader 做编码/版本验证。 fn load(&self, _uri: &str) -> io::Result { Ok(ResolvedEndorSource { revision: self.revision.into(), @@ -218,16 +270,22 @@ impl EndorSourceProvider for StaticProvider { } #[test] +/// 由 Rust 测试框架调用:URI 结果必须有非空版本身份;语法错误与编码错误都显示实际解析版本,空白标签被拒绝。 fn uri_snapshots_require_a_revision_and_preserve_it_in_source_diagnostics() { + // URI 作为宿主可解释的定位符,Reader 不把 namespace/function 猜成磁盘路径。 let uri = "endor:workspace/namespace/function"; + // 使用合法版本身份但语法错误的源码,错误显示应继续包含该版本。 let provider = StaticProvider { revision: "rev-one", bytes: b"function f(): Number { return ) }", }; + // 建立带来源名的原文快照,后续错误必须定位这份文本而非临时转换后的内容。 let source = EndorReader::from_endor(uri, &provider).unwrap(); + // 当前用例要求此阶段失败,随后检查具体类别、来源或底层原因,避免仅接受任意错误。 let error = parse_source(&source).unwrap_err(); assert_eq!(error.origin, source.origin().clone()); assert!(error.to_string().contains("[revision rev-one]")); + // 版本全为空白,即使字节是有效源码也必须在创建 URI 来源前拒绝。 let missing = StaticProvider { revision: " \t", bytes: b"1", @@ -236,18 +294,23 @@ fn uri_snapshots_require_a_revision_and_preserve_it_in_source_diagnostics() { EndorReader::from_endor(uri, &missing).unwrap_err().kind, SourceReadErrorKind::MissingRevision )); + // 版本有效但字节非法,编码失败说明中也应保留已解析版本身份。 let invalid = StaticProvider { revision: "rev-two", + // 借用测试静态字节,load 时复制给 Reader 解码。 bytes: &[0xff], }; + // 当前用例要求此阶段失败,随后检查具体类别、来源或底层原因,避免仅接受任意错误。 let error = EndorReader::from_endor(uri, &invalid).unwrap_err(); assert!(matches!(error.kind, SourceReadErrorKind::InvalidUtf8(_))); assert!(error.to_string().contains("[revision rev-two]")); } +/// 宿主拒绝授权的模拟,不提供任何替代源码或路径。 struct DeniedProvider; impl EndorSourceProvider for DeniedProvider { + /// from_endor 调用,始终返回宿主 PermissionDenied,禁止任何本地文件回退。 fn load(&self, _uri: &str) -> io::Result { Err(io::Error::new( io::ErrorKind::PermissionDenied, @@ -257,10 +320,13 @@ impl EndorSourceProvider for DeniedProvider { } #[test] +/// 由 Rust 测试框架调用:基础非法 URI 不触发宿主,合法 URI 被宿主拒绝时保留 PermissionDenied,不改走文件读取。 fn invalid_uris_do_not_call_the_provider_and_denials_do_not_fall_back_to_files() { + // 为本场景新建计数宿主,不与其他 URI 测试共享解析次数。 let provider = Provider { calls: Cell::new(0), }; + // 覆盖空定位符、空地址、错误 scheme、空白和控制字符,均应在访问 provider 前被拒绝。 for uri in [ "", "endor:", @@ -275,7 +341,9 @@ fn invalid_uris_do_not_call_the_provider_and_denials_do_not_fall_back_to_files() )); } assert_eq!(provider.calls.get(), 0); + // 当前用例要求此阶段失败,随后检查具体类别、来源或底层原因,避免仅接受任意错误。 let error = EndorReader::from_endor("endor:anywhere", &DeniedProvider).unwrap_err(); + // 必须能解构出原始 IO 错误,之后检查 NotFound 或 PermissionDenied 的具体种类。 let SourceReadErrorKind::Io(error) = error.kind else { panic!("expected provider error"); }; diff --git a/crates/frontend/tests/syntax.rs b/crates/frontend/tests/syntax.rs index 07aed3a..b57a185 100644 --- a/crates/frontend/tests/syntax.rs +++ b/crates/frontend/tests/syntax.rs @@ -1,30 +1,39 @@ +//! Rust 测试框架验证 Token、语法树形状、优先级、拥有文本及原文诊断;不进行名称分析或 VM 执行。 use frontend::ast::{ BinaryOp, Binding, DeclKind, Expr, ExprKind, Function, Program, StmtKind, UnaryOp, }; use frontend::{DiagnosticKind, Span, TokenKind, lex, parse, parse_expression}; +/// 声明测试借用 program 指定位置的函数,错误节点种类直接使测试失败,不创建或调用 VM 函数。 fn function(program: &Program, index: usize) -> &Function { + // 借用指定声明并要求确为函数,类等其他声明不应被辅助函数静默接受。 let DeclKind::Function(function) = &program.declarations[index].kind else { panic!("expected function") }; function } +/// 声明测试借用函数体指定位置的 let Binding,核对解析树语句形状与名称。 fn binding(function: &Function, index: usize) -> &Binding { + // 要求当前语句是 let 节点,普通表达式或赋值不满足此测试前提。 let StmtKind::Binding(binding) = &function.body.statements[index].kind else { panic!("expected binding") }; binding } +/// 声明测试借用函数体末尾 Return 的表达式,断言本用例确实有返回节点及值。 fn result(function: &Function) -> &Expr { + // 末语句必须是 Return,下面取得其中表达式,不把普通表达式语句误当返回。 let StmtKind::Return(returned) = &function.body.statements.last().unwrap().kind else { panic!("expected return") }; returned.value.as_ref().unwrap() } +/// 优先级测试拆二元节点,先核对操作种类再借用左右操作数,供逐层检查嵌套树。 fn binary(expression: &Expr, expected: BinaryOp) -> (&Expr, &Expr) { + // 拆出借用的操作符及两个子表达式,操作种类在返回前统一核对。 let ExprKind::Binary { operation, left, @@ -37,13 +46,17 @@ fn binary(expression: &Expr, expected: BinaryOp) -> (&Expr, &Expr) { (left, right) } +/// 表达式测试核对原始数值拼写,前端保存 String,不在这里转换或计算数值。 fn number(expression: &Expr, expected: &str) { assert_eq!(expression.kind, ExprKind::Number(expected.into())); } #[test] +/// 由 Rust 测试框架调用:Unicode 名称、全角数字及 emoji 字符串的 Token 范围精确切回原文,所有端点都在字符边界。 fn unicode_tokens_refer_to_exact_original_byte_ranges() { + // 保留当前原始文本用于字节切片与位置比较,不规范化其中的 Unicode、注释、空白或换行。 let source = "// 日本語\r\nlet 商品_2 = \"こんにちは🌸\""; + // 仅分词,后续同时检查种类与通过 span 切回的原文,不借助编译器判断数值。 let tokens = lex(source).unwrap(); assert_eq!(tokens.len(), 4); assert_eq!(tokens[0].kind, TokenKind::Name); @@ -51,6 +64,7 @@ fn unicode_tokens_refer_to_exact_original_byte_ranges() { assert_eq!(&source[tokens[1].span.start..tokens[1].span.end], "商品_2"); assert_eq!(tokens[2].kind, TokenKind::Equal); assert_eq!(tokens[3].kind, TokenKind::String); + // 每个半开范围的两端都必须是 UTF-8 边界,不能只检查特定一个名称。 for token in tokens { assert!(source.is_char_boundary(token.span.start)); assert!(source.is_char_boundary(token.span.end)); @@ -58,8 +72,11 @@ fn unicode_tokens_refer_to_exact_original_byte_ranges() { } #[test] +/// 由 Rust 测试框架调用:负号是独立 Token 和一元节点,长十进制的前导/尾随零在解析树中完整保留。 fn decimal_spelling_and_minus_tokens_are_preserved_without_float_conversion() { + // 保留当前原始文本用于字节切片与位置比较,不规范化其中的 Unicode、注释、空白或换行。 let source = "-000123456789012345678901234567890.00100 / 7 // comment"; + // 仅分词,后续同时检查种类与通过 span 切回的原文,不借助编译器判断数值。 let tokens = lex(source).unwrap(); assert_eq!( tokens.iter().map(|token| token.kind).collect::>(), @@ -74,8 +91,11 @@ fn decimal_spelling_and_minus_tokens_are_preserved_without_float_conversion() { &source[tokens[1].span.start..tokens[1].span.end], "000123456789012345678901234567890.00100" ); + // 解析完整表达式得到拥有型节点,下面直接核对语法结构而非运行结果。 let expression = parse_expression(source).unwrap(); + // 根为 Divide;左子树应是取负,右子树保持原始数字 7。 let (negative, divisor) = binary(&expression, BinaryOp::Divide); + // 当前层必须是前缀取负节点,后面的操作数继续用于检查内部树。 let ExprKind::Unary { operation: UnaryOp::Negate, operand, @@ -88,7 +108,9 @@ fn decimal_spelling_and_minus_tokens_are_preserved_without_float_conversion() { } #[test] +/// 由 Rust 测试框架调用:四种支持转义被解码,字符串内双斜杠作为文本保留,节点范围仍覆盖带引号原文。 fn strings_decode_only_the_supported_escapes_and_keep_comments_inside_strings() { + // 解析完整表达式得到拥有型节点,下面直接核对语法结构而非运行结果。 let expression = parse_expression(r#""商品\n\t\"\\// text""#).unwrap(); assert_eq!( expression.kind, @@ -101,7 +123,9 @@ fn strings_decode_only_the_supported_escapes_and_keep_comments_inside_strings() } #[test] +/// 由 Rust 测试框架调用:坏字符串、未支持数字后缀/格式和未知字符在词法阶段失败,诊断端点仍是合法原文字节边界。 fn malformed_strings_numbers_and_unknown_characters_report_lexical_errors() { + // 枚举非法输入;每例必须整体失败,不能只成功消费一个前缀就返回 AST。 for source in [ "\"unfinished", "\"bad\\q\"", @@ -117,6 +141,7 @@ fn malformed_strings_numbers_and_unknown_characters_report_lexical_errors() { "🌸", "\0", ] { + // 要求失败并检查阶段/范围/位置,确保不是被其他解析路径误接收。 let error = lex(source).unwrap_err(); assert_eq!(error.kind, DiagnosticKind::Lexical, "{source:?}"); assert!(error.span.start <= error.span.end); @@ -124,31 +149,44 @@ fn malformed_strings_numbers_and_unknown_characters_report_lexical_errors() { assert!(source.is_char_boundary(error.span.start)); assert!(source.is_char_boundary(error.span.end)); } + // 保留当前原始文本用于字节切片与位置比较,不规范化其中的 Unicode、注释、空白或换行。 let source = "\"日\\q\""; + // 要求失败并检查阶段/范围/位置,确保不是被其他解析路径误接收。 let error = lex(source).unwrap_err(); assert_eq!(&source[error.span.start..error.span.end], "\\q"); assert_eq!(error.location(source).column, 3); } #[test] +/// 由 Rust 测试框架调用:逐层拆 AST 验证乘除优先于加减、同层左结合、括号扩大节点范围及连续负号保持两层一元节点。 fn arithmetic_precedence_and_left_associativity_form_the_expected_tree() { + // 解析完整表达式得到拥有型节点,下面直接核对语法结构而非运行结果。 let expression = parse_expression("1 - 2 - 3 * 4 / 5 + 6").unwrap(); + // 最外层是最后的加法,右侧是 6,左侧包含前面的减法链及乘除。 let (subtract, last) = binary(&expression, BinaryOp::Add); number(last, "6"); + // 第二次减法的左侧仍是第一次减法,体现同层左结合。 let (first_subtract, divide) = binary(subtract, BinaryOp::Subtract); + // 第一次减法直接连接数字 1 和 2。 let (first, second) = binary(first_subtract, BinaryOp::Subtract); number(first, "1"); number(second, "2"); + // 乘除同层左结合,因此除法左边是 3*4,右边是 5。 let (multiply, fifth) = binary(divide, BinaryOp::Divide); number(fifth, "5"); + // 乘法的两个叶子分别为数字 3 和 4。 let (third, fourth) = binary(multiply, BinaryOp::Multiply); number(third, "3"); number(fourth, "4"); + // 括号让加法作为乘法左操作数,且加法节点范围包含括号本身。 let grouped = parse_expression("(1 + 2) * 3").unwrap(); + // 根为 Multiply,取左边的括号加法验证优先级被分组覆盖。 let (sum, _) = binary(&grouped, BinaryOp::Multiply); binary(sum, BinaryOp::Add); assert_eq!(sum.span, Span::new(0, 7)); + // 两个负号在语法阶段保留两个节点,精确字面量符号合并属于之后的 codegen。 let nested = parse_expression("--1").unwrap(); + // 当前层必须是前缀取负节点,后面的操作数继续用于检查内部树。 let ExprKind::Unary { operand, .. } = nested.kind else { panic!("expected outer negate"); }; @@ -162,8 +200,11 @@ fn arithmetic_precedence_and_left_associativity_form_the_expected_tree() { } #[test] +/// 由 Rust 测试框架调用:取负包住整个 factory/成员/调用后缀链,逐层检查接收对象、符号实参、文本实参及属性名。 fn member_and_call_chains_bind_more_tightly_than_unary_minus() { + // 解析完整表达式得到拥有型节点,下面直接核对语法结构而非运行结果。 let expression = parse_expression("-factory(1).build(.title)(\"日\", 2)").unwrap(); + // 当前层必须是前缀取负节点,后面的操作数继续用于检查内部树。 let ExprKind::Unary { operation: UnaryOp::Negate, operand, @@ -171,22 +212,27 @@ fn member_and_call_chains_bind_more_tightly_than_unary_minus() { else { panic!("expected negate"); }; + // 拆下一层调用的被调子树及实参;同名变量在逐层拆解时遮蔽上一层借用/拥有值。 let ExprKind::Call { callee, arguments } = operand.kind else { panic!("expected last call"); }; assert_eq!(arguments.len(), 2); assert_eq!(arguments[0].kind, ExprKind::String("日".into())); + // 拆下一层调用的被调子树及实参;同名变量在逐层拆解时遮蔽上一层借用/拥有值。 let ExprKind::Call { callee, arguments } = callee.kind else { panic!("expected build call"); }; + // build 的第一个参数是 .title 符号节点,不是普通名称读取或成员访问。 let ExprKind::Symbol(name) = &arguments[0].kind else { panic!("expected a symbol"); }; assert_eq!(name.text, "title"); + // 成员节点保留 build 名称,其接收对象应是 factory(1) 调用。 let ExprKind::Member { object, name } = callee.kind else { panic!("expected member"); }; assert_eq!(name.text, "build"); + // 拆下一层调用的被调子树及实参;同名变量在逐层拆解时遮蔽上一层借用/拥有值。 let ExprKind::Call { callee, arguments } = object.kind else { panic!("expected factory call"); }; @@ -195,9 +241,12 @@ fn member_and_call_chains_bind_more_tightly_than_unary_minus() { } #[test] +/// 由 Rust 测试框架调用:解析示例和多函数源码,核对显式类型、绑定/返回、Unicode 原文范围及 AST 独立拥有文本。 fn parses_typed_functions_bindings_and_multiple_top_level_definitions() { + // 使用仓库原始示例,测试该文件仍是两个可独立解析的顶层函数声明。 let example = parse(include_str!("../examples/basic.endor")).unwrap(); assert_eq!(example.declarations.len(), 2); + // 保留当前原始文本用于字节切片与位置比较,不规范化其中的 Unicode、注释、空白或换行。 let source = r#" // Basic draft syntax, including a Unicode name. function discountedPrice(price: Number, discount: Number): Number { @@ -206,8 +255,10 @@ fn parses_typed_functions_bindings_and_multiple_top_level_definitions() { } function 表示(): String { return "こんにちは" } "#; + // 解析模块语法树,随后通过辅助函数核对声明、参数、语句及返回。 let program = parse(source).unwrap(); assert_eq!(program.declarations.len(), 2); + // 只借用第一个顶层函数,所有文本由 program 持有而非原文临时切片。 let first = function(&program, 0); assert_eq!(first.name.text, "discountedPrice"); assert_eq!(first.parameters.len(), 2); @@ -230,7 +281,9 @@ fn parses_typed_functions_bindings_and_multiple_top_level_definitions() { "function 表示(): String { return \"こんにちは\" }" ); // The output owns its text and can outlive the input buffer. + // 内层 String 离开块后销毁,外层 Program 仍应拥有函数名和字面量文本。 let owned = { + // 保留当前原始文本用于字节切片与位置比较,不规范化其中的 Unicode、注释、空白或换行。 let source = String::from("function f(): String { return \"ok\" }"); parse(&source).unwrap() }; @@ -238,13 +291,16 @@ fn parses_typed_functions_bindings_and_multiple_top_level_definitions() { } #[test] +/// 由 Rust 测试框架调用:关键字形名称可在其他上下文作标识符,CRLF 可出现在参数/块边界而不改变树结构。 fn syntax_words_are_contextual_and_block_layout_does_not_depend_on_newlines() { + // 解析模块语法树,随后通过辅助函数核对声明、参数、语句及返回。 let program = parse("function class(function: Type): Type { let agent = function return agent }") .unwrap(); assert_eq!(function(&program, 0).name.text, "class"); assert_eq!(function(&program, 0).parameters[0].name.text, "function"); assert_eq!(binding(function(&program, 0), 0).name.text, "agent"); + // 在语法分隔点插入 CRLF,验证空白布局不依赖特定换行格式。 let multiline = parse("function f\r\n(\r\nx: Number\r\n): Number\r\n{\r\nreturn x\r\n}").unwrap(); assert_eq!( @@ -254,7 +310,9 @@ fn syntax_words_are_contextual_and_block_layout_does_not_depend_on_newlines() { } #[test] +/// 由 Rust 测试框架调用:模块与表达式都拒绝空输入、剩余 Token、缺少必需语法及当前未支持形式。 fn parser_requires_complete_input_and_rejects_unimplemented_or_malformed_syntax() { + // 枚举非法输入;每例必须整体失败,不能只成功消费一个前缀就返回 AST。 for source in [ "", "// empty", @@ -270,6 +328,7 @@ fn parser_requires_complete_input_and_rejects_unimplemented_or_malformed_syntax( ] { assert!(parse(source).is_err(), "{source}"); } + // 枚举非法输入;每例必须整体失败,不能只成功消费一个前缀就返回 AST。 for source in [ "", "x y", "1 +", "+1", ".5", "1.", "f(1,)", "f(,1)", "f(1 2)", "(1", "obj.", "[1]", "1.2.3", @@ -279,20 +338,27 @@ fn parser_requires_complete_input_and_rejects_unimplemented_or_malformed_syntax( } #[test] +/// 由 Rust 测试框架调用:语法 Token 索引准确转回原文违规字符或 EOF 空范围,CRLF 和 Unicode 不破坏显示行列。 fn syntax_errors_map_token_indexes_to_utf8_source_and_eof_locations() { + // 保留当前原始文本用于字节切片与位置比较,不规范化其中的 Unicode、注释、空白或换行。 let source = "function f(): String {\r\n let 商品 = \"日\"\r\n return )\r\n}"; + // 要求失败并检查阶段/范围/位置,确保不是被其他解析路径误接收。 let error = parse(source).unwrap_err(); assert_eq!(error.kind, DiagnosticKind::Syntax); assert_eq!(&source[error.span.start..error.span.end], ")"); assert_eq!(error.location(source).line, 3); assert_eq!(error.location(source).column, 10); assert!(!error.expected.is_empty()); + // 保留当前原始文本用于字节切片与位置比较,不规范化其中的 Unicode、注释、空白或换行。 let source = "function f(): Number { return 1\r\n "; + // 要求失败并检查阶段/范围/位置,确保不是被其他解析路径误接收。 let error = parse(source).unwrap_err(); assert_eq!(error.span, Span::new(source.len(), source.len())); assert_eq!(error.location(source).line, 2); assert_eq!(error.location(source).column, 3); + // 保留当前原始文本用于字节切片与位置比较,不规范化其中的 Unicode、注释、空白或换行。 let source = "\r\n\r\n🌸"; + // 要求失败并检查阶段/范围/位置,确保不是被其他解析路径误接收。 let error = lex(source).unwrap_err(); assert_eq!(error.location(source).line, 3); assert_eq!(error.location(source).column, 1); -- 2.52.0 From 89f6aaecf773ba1db5b7f00f331d820341cd1b45 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 18:58:01 +0800 Subject: [PATCH 45/62] docs(tooling): explain AST generator and validation scripts --- scripts/check.sh | 8 ++ scripts/generate_ast.py | 181 +++++++++++++++++++++++++++++++++++ scripts/test_generate_ast.py | 18 ++++ 3 files changed, 207 insertions(+) diff --git a/scripts/check.sh b/scripts/check.sh index 2a5e793..f878b33 100644 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -1,8 +1,16 @@ #!/bin/sh +# 项目完整检查入口,供开发者或 CI 调用;下方 Cargo 测试和 lint 会触发编译。 +# 启用首个失败即退出和未定义变量报错,避免前置检查失败后继续报告后续成功。 set -eu +# 比较 ASDL 对应的已提交生成源码/契约是否过期;只检查,不写目标文件,内部会格式化 Rust 文本。 python3 scripts/generate_ast.py --check +# 运行生成器的纯 Python 回归场景,验证递归装箱、跨目标同步及非法 schema 拒绝。 python3 scripts/test_generate_ast.py +# 检查整个 Rust workspace 格式,不改文件;不运行 VM。 cargo fmt --all --check +# 单独检查 include 引入的两个 PEG 文件格式,补足普通 Cargo 源码格式检查的覆盖。 rustfmt --edition 2024 --check crates/frontend/src/lexer.peg crates/frontend/src/grammar.peg +# 编译并运行 workspace 测试,locked 要求使用已锁定依赖;这一步会产生构建产物。 cargo test --workspace --locked +# 对全部目标执行 Clippy,包含编译检查,并把所有警告作为错误拒绝。 cargo clippy --workspace --all-targets --locked -- -D warnings diff --git a/scripts/generate_ast.py b/scripts/generate_ast.py index 7c06570..cce2d9b 100644 --- a/scripts/generate_ast.py +++ b/scripts/generate_ast.py @@ -15,85 +15,128 @@ import re import subprocess +# ASDL 内置标量到 Rust 类型的映射;自定义节点类型另由 definitions 解析,不在这里注册。 BUILTINS = {"string": "String", "span": "Span", "schema_id": "String"} +# 所有生成源码共用的来源提示;修改生成注释应改模板,不手改派生文件。 HEADER = "// Generated by scripts/generate_ast.py from Endor.asdl. Do not edit.\n" +# parse_schema 拒绝 Rust 保留字标识符,避免输出无法表示的字段/类型名;schema 名称必须可跨目标使用。 RESERVED = set("as break const continue crate else enum extern false fn for if impl in let loop match mod move mut pub ref return self Self static struct super trait true type unsafe use where while async await dyn abstract become box do final macro override priv typeof unsized virtual yield try gen".split()) @dataclass(frozen=True) +# 不可变字段描述,由 parse_schema 构造,Rust/TS/JSON 及遍历器生成阶段共同消费。 class Field: + # 字段引用的 ASDL 类型名,可能是 BUILTINS 标量或用户定义节点。 type: str + # 字段、构造器或定义的原始标识符,生成器不自动改写它。 name: str + # 空串为必选单值,星号为序列,问号为可空/可缺省字段。 quantifier: str = "" @dataclass(frozen=True) +# 和类型的一个构造器;fields 可空、单项或多项,对应三种不同生成形状。 class Variant: + # 字段、构造器或定义的原始标识符,生成器不自动改写它。 name: str + # 产品类型字段或构造器字段,保持 ASDL 顺序;Definition 的 None 才表示和类型。 fields: tuple[Field, ...] @dataclass(frozen=True) +# 一个命名定义;fields 非 None 为产品类型,None 为带 variants 的和类型。 class Definition: + # 字段、构造器或定义的原始标识符,生成器不自动改写它。 name: str + # 产品类型字段或构造器字段,保持 ASDL 顺序;Definition 的 None 才表示和类型。 fields: tuple[Field, ...] | None + # 和类型构造器列表,产品类型保持为空元组。 variants: tuple[Variant, ...] = () + # 图分析与类型校验调用;产品直接取字段,和类型按构造器顺序展平所有字段。 def all_fields(self): return self.fields if self.fields is not None else tuple(f for v in self.variants for f in v.fields) + # Rust/TS/Schema 生成共用;只有全部构造器均无载荷的和类型才可作为简单枚举。 def simple(self): return self.fields is None and all(not v.fields for v in self.variants) +# generate 及生成器回归测试调用;解析本项目 ASDL 子集,拒绝未知语法、重名与未定义字段类型。 def parse_schema(text: str) -> dict[str, Definition]: """Parse the project's ASDL subset; reject unknown syntax and field types.""" + # ASDL 双横线注释不影响语法解析;generate 的协议哈希仍使用未经删注释的原文。 text = re.sub(r"--[^\n]*", "", text) + # 保存标识符及结构标点,后面的递归小解析器只按此序列推进。 tokens = [] + # ASDL 原始文本的 Python 字符索引,用于词法扫描错误定位片段。 cursor = 0 + # 只接受 ASCII 标识符和已支持的 ASDL 标点,其他字符不能被静默跳过。 pattern = re.compile(r"\s*([A-Za-z_][A-Za-z_0-9]*|[{}()=|,*?])") + # 按输入顺序扫描 Token,纯尾部空白不生成额外 Token。 while cursor < len(text): + # 只有剩余部分全为空白才正常结束,非空未知内容必须进入失败路径。 if not text[cursor:].strip(): break + # 从当前位置匹配一个含前导空白的 Token,禁止跳过未识别片段向后搜索。 match = pattern.match(text, cursor) + # 未知字符直接报错,避免生成不完整的部分 schema。 if not match: raise ValueError(f"unsupported ASDL syntax near {text[cursor:cursor + 30]!r}") tokens.append(match[1]) cursor = match.end() + # 语法游标与前面的文本游标分开,指向尚未消费的 Token。 index = 0 + # parse_schema 内部前瞻,不前进游标;不完整输入在这里明确报告 EOF。 def peek(): + # 无可消费/前瞻 Token 时报告输入不完整,不让 Python 索引异常泄漏为协议。 if index == len(tokens): raise ValueError("unexpected end of ASDL") return tokens[index] + # 内部消费一个 Token,可同时校验固定标点;只有成功后才推进 index。 def take(expected=None): + # 多个内部解析函数共享外层 Token 游标,不能各自维护副本。 nonlocal index + # 无可消费/前瞻 Token 时报告输入不完整,不让 Python 索引异常泄漏为协议。 if index == len(tokens): raise ValueError("unexpected end of ASDL") + # 保留待返回 Token,在验证期望值后再推进游标。 value = tokens[index] + # 固定词/标点与当前 Token 必须相同,错误显示两者便于定位 schema 编辑问题。 if expected is not None and value != expected: raise ValueError(f"expected {expected!r}, found {value!r}") index += 1 return value + # 定义名、类型名、构造器名及字段名共用的标识符检查。 def identifier(): + # 消费一个候选名称,然后检查允许的标识符形状与保留字约束。 value = take() + # 禁止未知命名形式及 Rust 保留字,以免后续模板生成不可用源码。 if not re.fullmatch(r"[A-Za-z_][A-Za-z_0-9]*", value) or value in RESERVED: raise ValueError(f"invalid or reserved identifier: {value}") return value + # 产品及带载荷构造器共用括号字段列表解析,返回不可变元组保留原顺序。 def fields(): take("(") + # 当前字段列表的累积结果,结束时再做字段名去重。 result = [] + # 右括号之前逐项读取类型、可选数量修饰符及字段名。 while peek() != ")": + # 先读字段类型,实际是否已定义在整个 schema 读完后统一检查,允许前向类型引用。 type_name = identifier() + # 仅问号或星号作为数量修饰,缺省是单个必需字段。 quantifier = take() if peek() in ("?", "*") else "" result.append(Field(type_name, identifier(), quantifier)) + # 没有逗号则结束列表,随后必须读到闭括号。 if peek() != ",": break take(",") take(")") + # 同一产品/构造器内不允许重复字段名。 if len({f.name for f in result}) != len(result): raise ValueError("duplicate field name") return tuple(result) @@ -101,110 +144,160 @@ def parse_schema(text: str) -> dict[str, Definition]: take("module") identifier() take("{") + # 按声明顺序保留定义;后续生成及 DFS 使用稳定顺序以得到可重复输出。 definitions = {} + # 逐个读取命名定义,模块闭括号结束后不允许残留输入。 while peek() != "}": + # 读取当前定义名并检查是否覆盖已定义类型或内置标量。 name = identifier() + # 自定义类型不能重名或覆写内置类型映射。 if name in definitions or name in BUILTINS: raise ValueError(f"duplicate or builtin type: {name}") take("=") + # 等号后直接括号表示产品;否则解析一个或多个和类型构造器。 if peek() == "(": + # 产品类型保存字段元组,区别于和类型的 fields=None。 definition = Definition(name, fields()) else: + # 按顺序收集当前和类型构造器,供输出判别联合/枚举。 variants = [] + # 持续收集由竖线分隔的构造器,遇到非竖线后交外层处理下一定义。 while True: + # 当前构造器名称,是否带载荷由接下来的左括号决定。 variant = identifier() + # 没有括号的构造器使用空元组,生成时成为无载荷分支。 members = fields() if peek() == "(" else () variants.append(Variant(variant, members)) + # 构造器列表结束;不提前消费下一定义的标识符。 if peek() != "|": break take("|") + # 同一和类型的构造器必须唯一,否则 JSON 判别标签会歧义。 if len({v.name for v in variants}) != len(variants): raise ValueError(f"duplicate constructor in {name}") + # 和类型以 None 区别产品的空字段元组,保留完整构造器列表。 definition = Definition(name, None, tuple(variants)) definitions[name] = definition take("}") + # 模块闭括号之后必须 EOF,拒绝尾随文本。 if index != len(tokens): raise ValueError("trailing ASDL input") + # 依稳定 schema 顺序处理每个定义,保留生成结果的可重复性。 for definition in definitions.values(): + # 产品及所有构造器字段都必须引用内置标量或已声明类型。 for field in definition.all_fields(): + # 解析完所有声明再检查类型,因此支持合法前向引用但拒绝拼错的未知类型。 if field.type not in definitions and field.type not in BUILTINS: raise ValueError(f"undefined field type: {field.type}") return definitions +# Rust 访问器方法名生成调用,把类型名大写边界转成蛇形小写,不改变 schema 原类型名。 def snake(name): return re.sub(r"(?" + # 仅 DFS 回边添加 Box;可选字段之后再包 Option,以保留空值语义。 if (owner, field.type) in boxed: value = f"Box<{value}>" return f"Option<{value}>" if field.quantifier == "?" else value lines = [HEADER, "use crate::Span;", "use super::Spanned;", f'pub const AST_SCHEMA_ID: &str = "{schema_id}";'] + # 依稳定 schema 顺序处理每个定义,保留生成结果的可重复性。 for definition in definitions.values(): + # 所有 AST 类型支持拥有副本、相等比较及 serde,简单无载荷枚举才额外 Copy。 derives = "Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize" + # 无载荷和类型可用简单枚举/字符串联合表示,无需 kind/data 的载荷包装。 if definition.simple(): derives += ", Copy" lines.append(f"#[derive({derives})]") + # 产品及带载荷和类型拒绝未知字段;后者额外采用 kind/data 邻接标签。 if not definition.simple(): + # 只有和类型才加 kind/data 标签,产品字段直接序列化成对象。 tag = 'tag = "kind", content = "data", ' if definition.fields is None else "" lines.append(f"#[serde({tag}deny_unknown_fields)]") + # 产品按字段顺序生成;和类型则按构造器分支选择对应形状。 if definition.fields is not None: lines.append(f"pub struct {definition.name} {{") lines.extend(f"pub {f.name}: {field_type(definition.name, f)}," for f in definition.fields) else: lines.append(f"pub enum {definition.name} {{") + # 每个构造器按稳定顺序生成,不合并名称相近但身份不同的分支。 for variant in definition.variants: + # 无载荷构造器不携带额外字段,单值和多字段载荷分别采用相应表示。 if not variant.fields: lines.append(f"{variant.name},") + # 单字段构造器直接包装字段类型,多字段才使用命名字段对象。 elif len(variant.fields) == 1: lines.append(f"{variant.name}({field_type(definition.name, variant.fields[0])}),") else: + # 多字段构造器的具名成员保留每个字段自己的类型/数量修饰。 fields = ", ".join(f"{f.name}: {field_type(definition.name, f)}" for f in variant.fields) lines.append(f"{variant.name} {{ {fields} }},") lines.append("}") + # 只为结构上可定位的类型生成 Spanned,不编造 ExprKind 这类混合载荷的范围。 if definition.name in spanned: lines.append(f"impl Spanned for {definition.name} {{ fn span(&self) -> Span {{") if definition.fields is not None: @@ -217,15 +310,20 @@ def rust_types(definitions, schema_id): return "\n".join(lines) + "\n" +# generate 生成 Rust 借用节点访问器及 walk 函数;可定位节点通过 enter/leave 支持校验状态栈。 def rust_visitor(definitions): + # 取得结构上可提供跨度的类型集合,决定 span 实现或访问器节点钩子。 spanned = spanned_types(definitions) + # 按 schema 顺序导入所有节点类型,walk 可以直接引用它们。 names = ", ".join(definitions) lines = [HEADER, f"use super::{{{names}, Spanned}};", "use crate::Span;", "pub trait Visitor {", "type Error;", "fn enter_node(&mut self, _span: Span) -> Result<(), Self::Error> { Ok(()) }", "fn leave_node(&mut self) {}", "fn visit_span(&mut self, _span: &Span) -> Result<(), Self::Error> { Ok(()) }"] + # 按定义顺序遍历/输出每个命名类型,避免由无序集合决定源码结构。 for name in definitions: lines.append(f"fn visit_{snake(name)}(&mut self, node: &{name}) -> Result<(), Self::Error> {{") + # 可定位节点先进入,再保存子遍历 Result,退出后才返回错误,保证成功进入祖先的钩子配平。 if name in spanned: lines += ["self.enter_node(node.span())?;", f"let result = walk_{snake(name)}(self, node);", "self.leave_node();", "result"] else: @@ -233,38 +331,59 @@ def rust_visitor(definitions): lines.append("}") lines.append("}") + # 访问器生成共用的字段递归模板:跳过普通文本/标识,仅递归节点或显式跨度。 def children(field, access): + # 字符串和 schema_id 无语法子节点,不能生成不存在的 visit_string/walkstring 调用。 if field.type not in definitions and field.type != "span": return [] + # Rust 字段类型名映射成 visitor.visit_*,span 走默认可覆盖的 visit_span。 method = f"visitor.visit_{snake(field.type)}" + # 序列优先映射到 Vec/Array/array;容器本身处理重复元素并打断 Rust 按值递归。 if field.quantifier == "*": return [f"for value in {access} {{ {method}(value)?; }}"] + # 可选字段只有 Some/非空时才递归,缺省或 null 不访问。 if field.quantifier == "?": return [f"if let Some(value) = {access} {{ {method}(value)?; }}"] return [f"{method}({access})?;"] + # 依稳定 schema 顺序处理每个定义,保留生成结果的可重复性。 for definition in definitions.values(): + # 为当前 walk 函数累积字段/构造器递归语句,不在生成器内运行访问。 body = [] + # 产品按字段顺序生成;和类型则按构造器分支选择对应形状。 if definition.fields is not None: + # 产品字段依声明顺序递归,保证访问顺序与数据契约一致。 for field in definition.fields: body += children(field, f"&node.{field.name}") + # 带载荷和类型按判别分支访问,简单枚举没有子节点。 elif not definition.simple(): body.append("match node {") + # 每个构造器按稳定顺序生成,不合并名称相近但身份不同的分支。 for variant in definition.variants: + # 只提取节点或 span 载荷;纯文本载荷匹配时忽略,避免无用 Rust 绑定。 visited = [f for f in variant.fields if f.type in definitions or f.type == "span"] + # 无载荷构造器不携带额外字段,单值和多字段载荷分别采用相应表示。 if not variant.fields: + # 按构造器载荷形状生成 Rust match 模式,仅绑定需要访问的成员。 pattern = f"{definition.name}::{variant.name}" + # 单字段构造器直接包装字段类型,多字段才使用命名字段对象。 elif len(variant.fields) == 1: + # 按构造器载荷形状生成 Rust match 模式,仅绑定需要访问的成员。 pattern = f"{definition.name}::{variant.name}({'value' if visited else '_'})" else: + # 多字段构造器只绑定需要递归的成员,其余通过 .. 忽略。 members = ", ".join(f.name for f in visited) + # 按构造器载荷形状生成 Rust match 模式,仅绑定需要访问的成员。 pattern = f"{definition.name}::{variant.name} {{ " + (members + ", " if members else "") + ".. }" body.append(pattern + " => {") + # 为每个被选中的载荷展开对应序列、可选或单值访问。 for field in visited: body += children(field, "value" if len(variant.fields) == 1 else field.name) body.append("}") body.append("}") + # 没有实际递归调用的 walker 使用下划线参数名,避免生成未使用变量告警。 visitor = "visitor" if any("visitor." in line for line in body) else "_visitor" + # 完全无子结构的 walker 也不使用 node,生成下划线名字以满足警告检查。 node = "node" if body else "_node" lines.append(f"pub fn walk_{snake(definition.name)}({visitor}: &mut V, {node}: &{definition.name}) -> Result<(), V::Error> {{") lines.extend(body) @@ -272,32 +391,48 @@ def rust_visitor(definitions): return "\n".join(lines) + "\n" +# generate 生成与 serde 形状匹配的 TypeScript 类型及可剪枝 walker,供编辑器消费 AST JSON。 def editor_types(definitions, schema_id): + # 编辑器字段映射:标量名、数组、可缺省/可空联合;schema_id 限定为当前常量的字面量类型。 def field_type(field): + # 将内置标量映射为 TypeScript 对应类型;命名节点保持 schema 名称。 result = {"string": "string", "span": "Span", "schema_id": "typeof AST_SCHEMA_ID"}.get(field.type, field.type) + # 序列优先映射到 Vec/Array/array;容器本身处理重复元素并打断 Rust 按值递归。 if field.quantifier == "*": result = f"Array<{result}>" + # 问号字段允许空值,目标类型/Schema 同时处理字段缺省。 elif field.quantifier == "?": result += " | null" return result + # TypeScript 产品或多载荷对象形状,问号字段标为可缺省,并保留其 null 可能性。 def object_type(fields): return "{ " + " ".join(f"{f.name}{'?' if f.quantifier == '?' else ''}: {field_type(f)};" for f in fields) + " }" lines = [HEADER, f'export const AST_SCHEMA_ID = "{schema_id}" as const;', "export interface Span { start: number; end: number; }"] + # 依稳定 schema 顺序处理每个定义,保留生成结果的可重复性。 for definition in definitions.values(): + # 产品按字段顺序生成;和类型则按构造器分支选择对应形状。 if definition.fields is not None: lines.append(f"export type {definition.name} = {object_type(definition.fields)};") else: + # 按顺序收集当前和类型构造器,供输出判别联合/枚举。 variants = [] + # 每个构造器按稳定顺序生成,不合并名称相近但身份不同的分支。 for variant in definition.variants: if definition.simple(): + # 拼接当前构造器的 kind 字面量和数据对象声明。 value = json.dumps(variant.name) + # 当前分支不是无载荷简单枚举,需要构造带 kind 及可选 data 的判别对象。 else: + # 无载荷分支没有 data 属性;确有载荷才生成相应 data 类型。 data = "" + # 仅有载荷的分支附加 data,单项直接取类型,多项包具名对象。 if variant.fields: + # 选择直接载荷或多字段对象,保持与 Rust serde 的 data 形状一致。 result = field_type(variant.fields[0]) if len(variant.fields) == 1 else object_type(variant.fields) data = f" data: {result};" + # 拼接当前构造器的 kind 字面量和数据对象声明。 value = '{ kind: ' + json.dumps(variant.name) + ';' + data + ' }' variants.append(value) lines.append(f"export type {definition.name} = " + " | ".join(variants) + ";") @@ -305,27 +440,38 @@ def editor_types(definitions, schema_id): lines.extend(f"visit{name}?(node: {name}): boolean | void;" for name in definitions) lines.append("}") + # 访问器生成共用的字段递归模板:跳过普通文本/标识,仅递归节点或显式跨度。 def children(field, access): + # 字符串和 schema_id 无语法子节点,不能生成不存在的 visit_string/walkstring 调用。 if field.type not in definitions and field.type != "span": return [] + # TS span 特殊走 walkSpan,其余命名类型使用对应 walk。 function = "walkSpan" if field.type == "span" else f"walk{field.type}" + # 序列优先映射到 Vec/Array/array;容器本身处理重复元素并打断 Rust 按值递归。 if field.quantifier == "*": return [f"for (const value of {access}) {function}(value, visitor);"] + # 可选字段只有 Some/非空时才递归,缺省或 null 不访问。 if field.quantifier == "?": return [f"if ({access} != null) {function}({access}, visitor);"] return [f"{function}({access}, visitor);"] lines.append("export function walkSpan(node: Span, visitor: Visitor): void { visitor.visitSpan?.(node); }") + # 依稳定 schema 顺序处理每个定义,保留生成结果的可重复性。 for definition in definitions.values(): lines += [f"export function walk{definition.name}(node: {definition.name}, visitor: Visitor): void {{", f"if (visitor.visit{definition.name}?.(node) === false) return;"] + # 产品按字段顺序生成;和类型则按构造器分支选择对应形状。 if definition.fields is not None: + # 产品字段依声明顺序递归,保证访问顺序与数据契约一致。 for field in definition.fields: lines += children(field, "node." + field.name) + # 带载荷和类型按判别分支访问,简单枚举没有子节点。 elif not definition.simple(): lines.append("switch (node.kind) {") + # 每个构造器按稳定顺序生成,不合并名称相近但身份不同的分支。 for variant in definition.variants: lines.append(f"case {json.dumps(variant.name)}:") for field in variant.fields: + # 单项载荷取 node.data,多字段载荷再按字段名取 node.data.。 access = "node.data" + ("." + field.name if len(variant.fields) > 1 else "") lines += children(field, access) lines.append("break;") @@ -334,41 +480,62 @@ def editor_types(definitions, schema_id): return "\n".join(lines) + "\n" +# generate 生成编辑器 JSON Schema,结构与 Rust serde/TS 对齐;范围包含关系仍由运行时文档校验负责。 def json_schema(definitions, schema_id): + # 字段映射为内置约束或 $ref,并按数量修饰包数组或可空 anyOf。 def field_schema(field): + # 内置 string、schema_id 常量和 Span 引用特殊处理;其他字段引用对应定义。 value = {"string": {"type": "string"}, "schema_id": {"const": schema_id}, "span": {"$ref": "#/$defs/Span"}}.get(field.type, {"$ref": f"#/$defs/{field.type}"}) + # 序列优先映射到 Vec/Array/array;容器本身处理重复元素并打断 Rust 按值递归。 if field.quantifier == "*": value = {"type": "array", "items": value} + # 问号字段允许空值,目标类型/Schema 同时处理字段缺省。 elif field.quantifier == "?": value = {"anyOf": [value, {"type": "null"}]} return value + # 产品和带标签构造器共用,默认所有属性必需,可选字段调用方提供较小 required 列表。 def object_schema(properties, required=None): return {"type": "object", "properties": properties, "required": list(properties) if required is None else required, "additionalProperties": False} + # Span 两端要求非负 JS 安全整数,但此 Schema 不表达 start<=end 或父子包含关系。 definitions_json = {"Span": object_schema({name: {"type": "integer", "minimum": 0, "maximum": 9007199254740991} for name in ["start", "end"]})} + # 依稳定 schema 顺序处理每个定义,保留生成结果的可重复性。 for definition in definitions.values(): + # 产品按字段顺序生成;和类型则按构造器分支选择对应形状。 if definition.fields is not None: + # 产品属性以字段名为键,只有非问号字段进入 required,且拒绝额外属性。 value = object_schema({f.name: field_schema(f) for f in definition.fields}, [f.name for f in definition.fields if f.quantifier != "?"]) + # 无载荷枚举映射成字符串 enum,与 serde 和 TS 字面量联合一致。 elif definition.simple(): + # 每个枚举项使用 schema 中的构造器原名。 value = {"enum": [v.name for v in definition.variants]} else: + # 按顺序收集当前和类型构造器,供输出判别联合/枚举。 variants = [] + # 每个构造器按稳定顺序生成,不合并名称相近但身份不同的分支。 for variant in definition.variants: + # 带载荷和类型先固定 kind 标签,有字段时再加入 data 约束。 properties = {"kind": {"const": variant.name}} if variant.fields: properties["data"] = field_schema(variant.fields[0]) if len(variant.fields) == 1 else object_schema({f.name: field_schema(f) for f in variant.fields}, [f.name for f in variant.fields if f.quantifier != "?"]) variants.append(object_schema(properties)) + # 联合要求恰匹配一个构造器,kind 常量把各分支区分开。 value = {"oneOf": variants} definitions_json[definition.name] = value return json.dumps({"$schema": "https://json-schema.org/draft/2020-12/schema", "$id": schema_id, "$ref": "#/$defs/AstDocument", "$defs": definitions_json}, ensure_ascii=False, indent=2) + "\n" +# main 和生成器回归测试调用的纯生成入口,返回目标相对路径到源码/Schema 文本,不写文件。 def generate(text): + # 先完成所有 ASDL 语法及引用校验,失败不返回部分产物。 definitions = parse_schema(text) + # 文档协议要求存在两个根定义,不能把任意 ASDL 片段当完整接口生成。 for required in ("AstDocument", "AstRoot"): + # 缺少入口类型时明确拒绝,避免输出互相引用但没有协议根的文件。 if required not in definitions: raise ValueError(f"missing required schema type: {required}") + # 协议身份对原始 ASDL 全文做 SHA-256,连注释或空白变化也会改变身份;缓存或传输文档应核对这一身份。 schema_id = "urn:endor:ast:sha256:" + hashlib.sha256(text.encode("utf-8")).hexdigest() return { "crates/frontend/src/ast/generated.rs": rust_types(definitions, schema_id), @@ -378,28 +545,42 @@ def generate(text): } +# 命令行 --check/--write 入口,先生成内存文本,Rust 交给 rustfmt,再按模式比较或写入源码文件。 def main(): + # 建立命令行参数说明,check 与 write 必须且只能选择一个。 parser = argparse.ArgumentParser(description=__doc__) + # 互斥参数组阻止检查和写入混用,避免验证命令隐式修改仓库。 mode = parser.add_mutually_exclusive_group(required=True) mode.add_argument("--check", action="store_true") mode.add_argument("--write", action="store_true") + # 解析参数后才定位仓库、读取 schema 及生成所有目标。 args = parser.parse_args() + # 脚本文件所在仓库根,不依赖用户运行命令时的工作目录。 root = Path(__file__).resolve().parents[1] + # 读取原始 ASDL 文本一次,generate 为四个目标共享同一 schema 身份。 outputs = generate((root / "crates/frontend/Endor.asdl").read_text(encoding="utf-8")) + # check 模式收集缺失或内容过期的相对路径,最终统一报告。 stale = [] + # 每个目标独立处理;仅 Rust 文本需标准格式化,TS/JSON 保留确定性模板输出。 for relative, content in outputs.items(): + # rustfmt 只格式化源码并输出 stdout,不运行 Cargo、编译器或 Endor 代码。 if relative.endswith(".rs"): content = subprocess.run(["rustfmt", "--edition", "2024", "--emit", "stdout"], input=content, text=True, capture_output=True, check=True).stdout + # 目标路径相对于固定仓库根,防止工作目录改变写入位置。 path = root / relative + # 显式写入模式才创建父目录和更新文件,供开发者将生成源码纳入提交。 if args.write: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(content, encoding="utf-8") + # 检查模式不写入;缺失目标或任意文本差异都算过期。 elif not path.is_file() or path.read_text(encoding="utf-8") != content: stale.append(relative) + # 存在过期目标时以非零状态退出,报告如何通过 --write 显式更新。 if stale: parser.exit(1, "stale generated AST files:\n" + "\n".join(stale) + "\nRun python3 scripts/generate_ast.py --write\n") print("AST artifacts generated." if args.write else "AST artifacts match Endor.asdl.") +# 直接运行脚本才解析命令行,测试导入 generate 等函数不会触发文件写入或格式化进程。 if __name__ == "__main__": main() diff --git a/scripts/test_generate_ast.py b/scripts/test_generate_ast.py index 03be89a..df8d43f 100644 --- a/scripts/test_generate_ast.py +++ b/scripts/test_generate_ast.py @@ -8,38 +8,54 @@ import unittest from generate_ast import generate, parse_schema, recursive_edges +# 从固定仓库路径读取真实 ASDL,全体测试以同一协议输入为基线,不改磁盘 schema。 SCHEMA = (Path(__file__).resolve().parents[1] / "crates/frontend/Endor.asdl").read_text(encoding="utf-8") +# unittest 直接调用下列回归场景;generate 仅返回内存文本,不触发 main 的 rustfmt 或文件写入。 class AstGenerationTests(unittest.TestCase): + # unittest 检查生成 TS 中每个 walk 调用都有声明,防止内置标量被错误生成为不存在的 walker。 def test_editor_walkers_only_call_declared_functions(self): + # 获取内存生成的编辑器源码,不导入或执行 TypeScript。 editor = generate(SCHEMA)["crates/frontend/editor/ast.ts"] + # 提取导出的 walk 函数名,用集合表示可调用的完整声明空间。 declared = set(re.findall(r"export function (walk\w+)\(", editor)) + # 提取所有 walk 调用名(含声明中的名字),每项必须属于 declared。 calls = set(re.findall(r"\b(walk\w+)\(", editor)) self.assertLessEqual(calls, declared) self.assertIn("walkSpan(node.span, visitor)", editor) + # unittest 在内存 schema 增加构造器,确认三种语言契约及协议身份跟着更新且生成结果确定。 def test_schema_changes_reach_all_languages_and_schema_identity(self): + # 原 schema 的全目标输出,作为修改后的身份和源码比较基线。 before = generate(SCHEMA) + # 只给 UnaryOp 增加 LogicalNot,不写原 ASDL 或生成文件;输出应包含新分支。 after = generate(SCHEMA.replace("UnaryOp = Negate", "UnaryOp = Negate | LogicalNot")) self.assertIn("LogicalNot", after["crates/frontend/src/ast/generated.rs"]) self.assertIn('"LogicalNot"', after["crates/frontend/editor/ast.ts"]) + # JSON Schema 在内存解析,检查 UnaryOp 的 enum 同样获得新项。 editor_schema = json.loads(after["crates/frontend/editor/ast.schema.json"]) self.assertIn("LogicalNot", editor_schema["$defs"]["UnaryOp"]["enum"]) self.assertNotEqual(before["crates/frontend/src/ast/generated.rs"], after["crates/frontend/src/ast/generated.rs"]) self.assertEqual(after, generate(SCHEMA.replace("UnaryOp = Negate", "UnaryOp = Negate | LogicalNot"))) + # unittest 增加可选自引用 Link,验证 Rust 的 Option、访问递归及 TS 可空/可缺省形状。 def test_recursive_optional_nodes_are_boxed_and_walked(self): + # 在模块右括号前追加测试定义,原始 SCHEMA 常量保持不变。 extended = SCHEMA.rsplit("}", 1)[0] + "Link = (Link? next)\n}" + # 一次生成得到所有目标源码,分别核对同一递归边在不同目标上的表示。 outputs = generate(extended) self.assertIn("pub next: Option>", outputs["crates/frontend/src/ast/generated.rs"]) self.assertIn("visitor.visit_link(value)?", outputs["crates/frontend/src/ast/visitor.rs"]) self.assertIn("next?: Link | null", outputs["crates/frontend/editor/ast.ts"]) + # 直接解析真实 schema 检查 DFS 选择的回边,不要求所有递归方向都装箱。 definitions = parse_schema(SCHEMA) self.assertIn(("ExprKind", "Expr"), recursive_edges(definitions)) self.assertNotIn(("Expr", "ExprKind"), recursive_edges(definitions)) + # unittest 覆盖未定义类型、重名、不完整、尾随输入及未知字符,均须失败而非输出部分类型。 def test_invalid_schemas_fail_instead_of_emitting_partial_types(self): + # 每份输入只针对一个非法结构,subTest 让失败报告指明当前 schema。 for schema in [ "module Endor { Root = (Missing value) }", "module Endor { Root = (string x, string x) }", @@ -49,9 +65,11 @@ class AstGenerationTests(unittest.TestCase): "module Endor { Root = A } trailing", "module Endor { Root = @ }", ]: + # 当前子用例要求 parse_schema 明确抛 ValueError,不接受静默成功或无关异常。 with self.subTest(schema=schema), self.assertRaises(ValueError): parse_schema(schema) +# 脚本直接执行时启动 unittest;被其他检查导入时不自动运行用例。 if __name__ == "__main__": unittest.main() -- 2.52.0 From 52e62d58393183f988d5d18f6d793e6d193e320b Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 19:06:33 +0800 Subject: [PATCH 46/62] docs(frontend): persist generated AST and visitor annotations --- crates/frontend/editor/ast.ts | 301 +++++++++++++++++++++++++++ crates/frontend/src/ast/generated.rs | 136 ++++++++++++ crates/frontend/src/ast/visitor.rs | 186 +++++++++++++++++ scripts/generate_ast.py | 181 ++++++++++++++-- 4 files changed, 788 insertions(+), 16 deletions(-) diff --git a/crates/frontend/editor/ast.ts b/crates/frontend/editor/ast.ts index 8f83e3e..9b2982a 100644 --- a/crates/frontend/editor/ast.ts +++ b/crates/frontend/editor/ast.ts @@ -1,245 +1,546 @@ // Generated by scripts/generate_ast.py from Endor.asdl. Do not edit. +// 与 Rust wire 共同核对的 ASDL 原文协议身份;生成源码注释不改变此值。 export const AST_SCHEMA_ID = "urn:endor:ast:sha256:21b0349ac295d9bc8cedcad8f07d8e2d45cf220e19e7c2fad10269555fcfd71a" as const; +// 原 UTF-8 半开字节范围;number 类型本身不验证安全整数、顺序或原文边界。 export interface Span { start: number; end: number; } +// 宿主/编辑器传输的版本化语法文档,wire 校验后供 compiler 使用。 +// schema: 协议身份,wire 按 AST_SCHEMA_ID 核对;不是源码内容哈希。 +// root: 拥有的语法根,文档校验及编译从这里递归。 export type AstDocument = { schema: typeof AST_SCHEMA_ID; root: AstRoot; }; +// parse_ast 按模式构造的模块、表达式或交互根,compiler 据此选择生成入口。 +// Module: 模块根,包含函数/类声明。 +// Module.body: body 保存 Program 类型的值。 +// Expression: 表达式根,编译后直接返回求值结果。 +// Expression.body: body 保存 Expr 类型的值。 +// Interactive: 交互根,包含一次完整提交。 +// Interactive.body: body 保存 InteractiveInput 类型的值。 export type AstRoot = { kind: "Module"; data: Program; } | { kind: "Expression"; data: Expr; } | { kind: "Interactive"; data: InteractiveInput; }; +// parser 复制的名称文本与位置,用于声明、类型注解或属性名。 +// text: 拥有名称文本,不依赖输入缓冲存活。 +// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 export type Name = { text: string; span: Span; }; +// 模块声明序列,compiler 先注册全部顶层名称再分析各函数体。 +// declarations: 按源码顺序的声明;源码模块解析要求非空,文档结构本身不限制数量。 +// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 export type Program = { declarations: Array; span: Span; }; +// 顶层/交互声明的种类及完整范围,compiler 按函数或类分派。 +// kind: 函数/类声明载荷,JSON 和类型采用 kind/data 标签。 +// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 export type Decl = { kind: DeclKind; span: Span; }; +// 函数与类声明的判别载荷,外层 Decl 另外保存完整跨度。 +// Function: 显式参数/返回注解与函数体,compiler 生成独立模板及源码签名;解析本身不调用函数。 +// Function.definition: definition 保存 Function 类型的值。 +// Class: 类名、可选基类、字段和方法,compiler 检查成员后生成构类指令。 +// Class.definition: definition 保存 Class 类型的值。 export type DeclKind = { kind: "Function"; data: Function; } | { kind: "Class"; data: Class; }; +// 类名、可选基类、字段和方法,compiler 检查成员后生成构类指令。 +// name: 类声明名与精确位置。 +// base: 显式基类名,可省略;当前 compiler 对省略项使用 Object。 +// fields: 按出现顺序的字段名称/类型注解,不含方法。 +// methods: 按出现顺序的方法;显式 self 约束由 compiler 检查。 +// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 export type Class = { name: Name; base?: Name | null; fields: Array; methods: Array; span: Span; }; +// 显式参数/返回注解与函数体,compiler 生成独立模板及源码签名;解析本身不调用函数。 +// name: 函数/方法名,代码模板沿用为诊断名称。 +// parameters: 位置参数列表,compiler 依次分配前部局部槽。 +// return_type: 返回类型名,生成源码签名后由运行时解析。 +// body: 独立函数作用域的语句体,当前源码不支持嵌套声明闭包。 +// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 export type Function = { name: Name; parameters: Array; return_type: Name; body: Block; span: Span; }; +// 函数参数或类字段共用的名称/类型注解,实际类型身份由运行时环境解析。 +// name: 参数/字段名,用于布局和重复检查。 +// type_name: 声明类型名,前端只保留文本与位置。 +// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 export type Parameter = { name: Name; type_name: Name; span: Span; }; +// 顺序语句块,compiler 检查确定初始化并按语句生成指令。 +// statements: 有序语句列表,不引入独立块级局部作用域。 +// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 export type Block = { statements: Array; span: Span; }; +// 语句种类及原文范围,compiler 处理副作用、控制流或返回。 +// kind: 具体语句载荷,外层另存整句范围。 +// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 export type Stmt = { kind: StmtKind; span: Span; }; +// 语句判别载荷,parser 选择分支,compiler 按绑定、赋值、控制流等规则处理。 +// Binding: let 新绑定,compiler 检查重名并分析右值后才标记目标已初始化。 +// Binding.binding: binding 保存 Binding 类型的值。 +// Assignment: 普通名称赋值,compiler 按作用域写局部槽或全局环境。 +// Assignment.assignment: assignment 保存 Assignment 类型的值。 +// MemberAssignment: 成员写入的对象、属性名与右值,compiler 生成 StoreAttr。 +// MemberAssignment.assignment: assignment 保存 MemberAssignment 类型的值。 +// If: 条件、then 和可选 else,compiler 合并继续路径的初始化集合并生成跳转。 +// If.statement: statement 保存 IfStatement 类型的值。 +// While: 循环条件与体,compiler 生成回边并处理循环可能零次执行。 +// While.statement: statement 保存 WhileStatement 类型的值。 +// Return: 函数返回,compiler 对缺省值补 Nil;当前源码语法要求 return 后有表达式。 +// Return.statement: statement 保存 ReturnStatement 类型的值。 +// Expression: 表达式载荷及完整范围,compiler 生成留下一个结果的求值序列。 +// Expression.expression: expression 保存 Expr 类型的值。 +// Declaration: 顶层/交互声明的种类及完整范围,compiler 按函数或类分派。 +// Declaration.declaration: declaration 保存 Decl 类型的值。 export type StmtKind = { kind: "Binding"; data: Binding; } | { kind: "Assignment"; data: Assignment; } | { kind: "MemberAssignment"; data: MemberAssignment; } | { kind: "If"; data: IfStatement; } | { kind: "While"; data: WhileStatement; } | { kind: "Return"; data: ReturnStatement; } | { kind: "Expression"; data: Expr; } | { kind: "Declaration"; data: Decl; }; +// let 新绑定,compiler 检查重名并分析右值后才标记目标已初始化。 +// name: let 目标名,重复声明由语义分析拒绝。 +// value: 绑定右值,本局部不能在初始化前读取自身。 +// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 export type Binding = { name: Name; value: Expr; span: Span; }; +// 普通名称赋值,compiler 按作用域写局部槽或全局环境。 +// name: 普通赋值目标,函数内首次赋值可预分配局部槽。 +// value: 先求右值再更新目标。 +// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 export type Assignment = { name: Name; value: Expr; span: Span; }; +// 成员写入的对象、属性名与右值,compiler 生成 StoreAttr。 +// object: 属性写入接收对象表达式,前端不求值。 +// name: 待写属性名,compiler 加入名字表。 +// value: 待写右值,当前 compiler 先于接收对象生成求值以匹配 VM 栈布局。 +// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 export type MemberAssignment = { object: Expr; name: Name; value: Expr; span: Span; }; +// 条件、then 和可选 else,compiler 合并继续路径的初始化集合并生成跳转。 +// condition: 条件表达式,运行时按真值协议选择分支。 +// body: 条件为真的 then 块。 +// otherwise: 可选 else 块,缺省时存在直接继续路径。 +// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 export type IfStatement = { condition: Expr; body: Block; otherwise?: Block | null; span: Span; }; +// 循环条件与体,compiler 生成回边并处理循环可能零次执行。 +// condition: 每轮重新求值的条件,假值退出。 +// body: 循环体,可能一次也不执行。 +// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 export type WhileStatement = { condition: Expr; body: Block; span: Span; }; +// 函数返回,compiler 对缺省值补 Nil;当前源码语法要求 return 后有表达式。 +// value: 可选返回表达式,手工 AST 缺省时由 compiler 补 Nil。 +// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 export type ReturnStatement = { value?: Expr | null; span: Span; }; +// 单次交互提交容器,parser 产生一条语句,compiler 也检查数量,不保存宿主会话环境。 +// statements: 当前 parse/compile 约定恰好一个完整提交的语句。 +// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 export type InteractiveInput = { statements: Array; span: Span; }; +// 表达式载荷及完整范围,compiler 生成留下一个结果的求值序列。 +// kind: 求值形状,不缓存名称解析或运行时类型。 +// span: 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 export type Expr = { kind: ExprKind; span: Span; }; +// 名称、字面量、等待、运算和调用等表达式载荷,范围由外层 Expr 提供。 +// Name: 变量引用文本,compiler 决定局部槽或全局索引。 +// Name.text: text 保存 string 类型的值。 +// Number: 原始十进制拼写,保留全部位数、前导零及尾零,不转浮点。 +// Number.spelling: spelling 保存 string 类型的值。 +// String: 已解码受支持转义的拥有文本,原引号范围在外层 Expr。 +// String.value: value 保存 string 类型的值。 +// Symbol: 点开头的符号名,前端可表示,当前 compiler 拒绝生成运行时值。 +// Symbol.name: 成员或符号名,不是已解析的运行时属性句柄。 +// Await: 等待子表达式,VM 执行上下文决定是否允许停放。 +// Await.operand: 一元子表达式,Rust 的递归回边间接存储。 +// Unary: 一元取负,可在纯数值链上合并符号。 +// Unary.operation: 运算种类,决定算术、比较或取负指令。 +// Unary.operand: 一元子表达式,Rust 的递归回边间接存储。 +// Binary: 左右操作数及算术/比较种类,保留解析优先级树。 +// Binary.operation: 运算种类,决定算术、比较或取负指令。 +// Binary.left: 二元左操作数,当前 compiler 先于右侧求值。 +// Binary.right: 二元右操作数,与左侧一起被运行时运算消费。 +// Member: 接收对象及属性名的读取。 +// Member.object: 成员读取接收对象。 +// Member.name: 成员或符号名,不是已解析的运行时属性句柄。 +// Call: 被调用表达式与位置实参,由 VM 实际求值。 +// Call.callee: 调用目标,先于实参求值。 +// Call.arguments: 有序位置实参,AST 不隐式添加接收者。 export type ExprKind = { kind: "Name"; data: string; } | { kind: "Number"; data: string; } | { kind: "String"; data: string; } | { kind: "Symbol"; data: Name; } | { kind: "Await"; data: Expr; } | { kind: "Unary"; data: { operation: UnaryOp; operand: Expr; }; } | { kind: "Binary"; data: { operation: BinaryOp; left: Expr; right: Expr; }; } | { kind: "Member"; data: { object: Expr; name: Name; }; } | { kind: "Call"; data: { callee: Expr; arguments: Array; }; }; +// 一元运算种类,compiler 合并纯数值负号或生成运行时取负。 +// Negate: 一元取负。 export type UnaryOp = "Negate"; +// 二元算术/比较种类,parser 保留优先级树,compiler 映射为对应 VM 操作。 +// Add: 加法。 +// Subtract: 减法。 +// Multiply: 乘法。 +// Divide: 除法。 +// Equal: 相等比较。 +// NotEqual: 不等比较。 +// Less: 小于比较。 +// LessEqual: 小于等于比较。 +// Greater: 大于比较。 +// GreaterEqual: 大于等于比较。 export type BinaryOp = "Add" | "Subtract" | "Multiply" | "Divide" | "Equal" | "NotEqual" | "Less" | "LessEqual" | "Greater" | "GreaterEqual"; +// 编辑器可选节点回调;walk 先回调,返回 false 只剪掉当前子树,其他值继续递归。 export interface Visitor { +// 跨度叶子回调没有子结构,其返回值不参与后续剪枝。 visitSpan?(node: Span): boolean | void; +// walkAstDocument 到达 AstDocument 时调用,false 跳过该节点的后续子字段。 visitAstDocument?(node: AstDocument): boolean | void; +// walkAstRoot 到达 AstRoot 时调用,false 跳过该节点的后续子字段。 visitAstRoot?(node: AstRoot): boolean | void; +// walkName 到达 Name 时调用,false 跳过该节点的后续子字段。 visitName?(node: Name): boolean | void; +// walkProgram 到达 Program 时调用,false 跳过该节点的后续子字段。 visitProgram?(node: Program): boolean | void; +// walkDecl 到达 Decl 时调用,false 跳过该节点的后续子字段。 visitDecl?(node: Decl): boolean | void; +// walkDeclKind 到达 DeclKind 时调用,false 跳过该节点的后续子字段。 visitDeclKind?(node: DeclKind): boolean | void; +// walkClass 到达 Class 时调用,false 跳过该节点的后续子字段。 visitClass?(node: Class): boolean | void; +// walkFunction 到达 Function 时调用,false 跳过该节点的后续子字段。 visitFunction?(node: Function): boolean | void; +// walkParameter 到达 Parameter 时调用,false 跳过该节点的后续子字段。 visitParameter?(node: Parameter): boolean | void; +// walkBlock 到达 Block 时调用,false 跳过该节点的后续子字段。 visitBlock?(node: Block): boolean | void; +// walkStmt 到达 Stmt 时调用,false 跳过该节点的后续子字段。 visitStmt?(node: Stmt): boolean | void; +// walkStmtKind 到达 StmtKind 时调用,false 跳过该节点的后续子字段。 visitStmtKind?(node: StmtKind): boolean | void; +// walkBinding 到达 Binding 时调用,false 跳过该节点的后续子字段。 visitBinding?(node: Binding): boolean | void; +// walkAssignment 到达 Assignment 时调用,false 跳过该节点的后续子字段。 visitAssignment?(node: Assignment): boolean | void; +// walkMemberAssignment 到达 MemberAssignment 时调用,false 跳过该节点的后续子字段。 visitMemberAssignment?(node: MemberAssignment): boolean | void; +// walkIfStatement 到达 IfStatement 时调用,false 跳过该节点的后续子字段。 visitIfStatement?(node: IfStatement): boolean | void; +// walkWhileStatement 到达 WhileStatement 时调用,false 跳过该节点的后续子字段。 visitWhileStatement?(node: WhileStatement): boolean | void; +// walkReturnStatement 到达 ReturnStatement 时调用,false 跳过该节点的后续子字段。 visitReturnStatement?(node: ReturnStatement): boolean | void; +// walkInteractiveInput 到达 InteractiveInput 时调用,false 跳过该节点的后续子字段。 visitInteractiveInput?(node: InteractiveInput): boolean | void; +// walkExpr 到达 Expr 时调用,false 跳过该节点的后续子字段。 visitExpr?(node: Expr): boolean | void; +// walkExprKind 到达 ExprKind 时调用,false 跳过该节点的后续子字段。 visitExprKind?(node: ExprKind): boolean | void; +// walkUnaryOp 到达 UnaryOp 时调用,false 跳过该节点的后续子字段。 visitUnaryOp?(node: UnaryOp): boolean | void; +// walkBinaryOp 到达 BinaryOp 时调用,false 跳过该节点的后续子字段。 visitBinaryOp?(node: BinaryOp): boolean | void; } +// 上层 walker/宿主调用的跨度叶子入口,仅通知 visitSpan,不验证输入或修改节点。 export function walkSpan(node: Span, visitor: Visitor): void { visitor.visitSpan?.(node); } +// 上层 walker 或宿主调用,先通知 AstDocument 回调,再按 ASDL 顺序访问子结构;不修改树。 export function walkAstDocument(node: AstDocument, visitor: Visitor): void { +// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。 if (visitor.visitAstDocument?.(node) === false) return; +// 递归访问 node.root 的 AstRoot 子结构或跨度。 walkAstRoot(node.root, visitor); } +// 上层 walker 或宿主调用,先通知 AstRoot 回调,再按 ASDL 顺序访问子结构;不修改树。 export function walkAstRoot(node: AstRoot, visitor: Visitor): void { +// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。 if (visitor.visitAstRoot?.(node) === false) return; +// 判别 kind 只选择当前载荷,普通文本/数字拼写没有进一步语法子结构。 switch (node.kind) { +// 模块根,包含函数/类声明。 case "Module": +// 递归访问 node.data 的 Program 子结构或跨度。 walkProgram(node.data, visitor); break; +// 表达式根,编译后直接返回求值结果。 case "Expression": +// 递归访问 node.data 的 Expr 子结构或跨度。 walkExpr(node.data, visitor); break; +// 交互根,包含一次完整提交。 case "Interactive": +// 递归访问 node.data 的 InteractiveInput 子结构或跨度。 walkInteractiveInput(node.data, visitor); break; } } +// 上层 walker 或宿主调用,先通知 Name 回调,再按 ASDL 顺序访问子结构;不修改树。 export function walkName(node: Name, visitor: Visitor): void { +// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。 if (visitor.visitName?.(node) === false) return; +// 递归访问 node.span 的 span 子结构或跨度。 walkSpan(node.span, visitor); } +// 上层 walker 或宿主调用,先通知 Program 回调,再按 ASDL 顺序访问子结构;不修改树。 export function walkProgram(node: Program, visitor: Visitor): void { +// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。 if (visitor.visitProgram?.(node) === false) return; +// 按声明顺序访问 node.declarations 列表中的每个 Decl 子节点。 for (const value of node.declarations) walkDecl(value, visitor); +// 递归访问 node.span 的 span 子结构或跨度。 walkSpan(node.span, visitor); } +// 上层 walker 或宿主调用,先通知 Decl 回调,再按 ASDL 顺序访问子结构;不修改树。 export function walkDecl(node: Decl, visitor: Visitor): void { +// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。 if (visitor.visitDecl?.(node) === false) return; +// 递归访问 node.kind 的 DeclKind 子结构或跨度。 walkDeclKind(node.kind, visitor); +// 递归访问 node.span 的 span 子结构或跨度。 walkSpan(node.span, visitor); } +// 上层 walker 或宿主调用,先通知 DeclKind 回调,再按 ASDL 顺序访问子结构;不修改树。 export function walkDeclKind(node: DeclKind, visitor: Visitor): void { +// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。 if (visitor.visitDeclKind?.(node) === false) return; +// 判别 kind 只选择当前载荷,普通文本/数字拼写没有进一步语法子结构。 switch (node.kind) { +// 显式参数/返回注解与函数体,compiler 生成独立模板及源码签名;解析本身不调用函数。 case "Function": +// 递归访问 node.data 的 Function 子结构或跨度。 walkFunction(node.data, visitor); break; +// 类名、可选基类、字段和方法,compiler 检查成员后生成构类指令。 case "Class": +// 递归访问 node.data 的 Class 子结构或跨度。 walkClass(node.data, visitor); break; } } +// 上层 walker 或宿主调用,先通知 Class 回调,再按 ASDL 顺序访问子结构;不修改树。 export function walkClass(node: Class, visitor: Visitor): void { +// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。 if (visitor.visitClass?.(node) === false) return; +// 递归访问 node.name 的 Name 子结构或跨度。 walkName(node.name, visitor); +// node.base 可缺省或为 null,只有存在时才访问该子节点。 if (node.base != null) walkName(node.base, visitor); +// 按声明顺序访问 node.fields 列表中的每个 Parameter 子节点。 for (const value of node.fields) walkParameter(value, visitor); +// 按声明顺序访问 node.methods 列表中的每个 Function 子节点。 for (const value of node.methods) walkFunction(value, visitor); +// 递归访问 node.span 的 span 子结构或跨度。 walkSpan(node.span, visitor); } +// 上层 walker 或宿主调用,先通知 Function 回调,再按 ASDL 顺序访问子结构;不修改树。 export function walkFunction(node: Function, visitor: Visitor): void { +// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。 if (visitor.visitFunction?.(node) === false) return; +// 递归访问 node.name 的 Name 子结构或跨度。 walkName(node.name, visitor); +// 按声明顺序访问 node.parameters 列表中的每个 Parameter 子节点。 for (const value of node.parameters) walkParameter(value, visitor); +// 递归访问 node.return_type 的 Name 子结构或跨度。 walkName(node.return_type, visitor); +// 递归访问 node.body 的 Block 子结构或跨度。 walkBlock(node.body, visitor); +// 递归访问 node.span 的 span 子结构或跨度。 walkSpan(node.span, visitor); } +// 上层 walker 或宿主调用,先通知 Parameter 回调,再按 ASDL 顺序访问子结构;不修改树。 export function walkParameter(node: Parameter, visitor: Visitor): void { +// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。 if (visitor.visitParameter?.(node) === false) return; +// 递归访问 node.name 的 Name 子结构或跨度。 walkName(node.name, visitor); +// 递归访问 node.type_name 的 Name 子结构或跨度。 walkName(node.type_name, visitor); +// 递归访问 node.span 的 span 子结构或跨度。 walkSpan(node.span, visitor); } +// 上层 walker 或宿主调用,先通知 Block 回调,再按 ASDL 顺序访问子结构;不修改树。 export function walkBlock(node: Block, visitor: Visitor): void { +// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。 if (visitor.visitBlock?.(node) === false) return; +// 按声明顺序访问 node.statements 列表中的每个 Stmt 子节点。 for (const value of node.statements) walkStmt(value, visitor); +// 递归访问 node.span 的 span 子结构或跨度。 walkSpan(node.span, visitor); } +// 上层 walker 或宿主调用,先通知 Stmt 回调,再按 ASDL 顺序访问子结构;不修改树。 export function walkStmt(node: Stmt, visitor: Visitor): void { +// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。 if (visitor.visitStmt?.(node) === false) return; +// 递归访问 node.kind 的 StmtKind 子结构或跨度。 walkStmtKind(node.kind, visitor); +// 递归访问 node.span 的 span 子结构或跨度。 walkSpan(node.span, visitor); } +// 上层 walker 或宿主调用,先通知 StmtKind 回调,再按 ASDL 顺序访问子结构;不修改树。 export function walkStmtKind(node: StmtKind, visitor: Visitor): void { +// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。 if (visitor.visitStmtKind?.(node) === false) return; +// 判别 kind 只选择当前载荷,普通文本/数字拼写没有进一步语法子结构。 switch (node.kind) { +// let 新绑定,compiler 检查重名并分析右值后才标记目标已初始化。 case "Binding": +// 递归访问 node.data 的 Binding 子结构或跨度。 walkBinding(node.data, visitor); break; +// 普通名称赋值,compiler 按作用域写局部槽或全局环境。 case "Assignment": +// 递归访问 node.data 的 Assignment 子结构或跨度。 walkAssignment(node.data, visitor); break; +// 成员写入的对象、属性名与右值,compiler 生成 StoreAttr。 case "MemberAssignment": +// 递归访问 node.data 的 MemberAssignment 子结构或跨度。 walkMemberAssignment(node.data, visitor); break; +// 条件、then 和可选 else,compiler 合并继续路径的初始化集合并生成跳转。 case "If": +// 递归访问 node.data 的 IfStatement 子结构或跨度。 walkIfStatement(node.data, visitor); break; +// 循环条件与体,compiler 生成回边并处理循环可能零次执行。 case "While": +// 递归访问 node.data 的 WhileStatement 子结构或跨度。 walkWhileStatement(node.data, visitor); break; +// 函数返回,compiler 对缺省值补 Nil;当前源码语法要求 return 后有表达式。 case "Return": +// 递归访问 node.data 的 ReturnStatement 子结构或跨度。 walkReturnStatement(node.data, visitor); break; +// 表达式载荷及完整范围,compiler 生成留下一个结果的求值序列。 case "Expression": +// 递归访问 node.data 的 Expr 子结构或跨度。 walkExpr(node.data, visitor); break; +// 顶层/交互声明的种类及完整范围,compiler 按函数或类分派。 case "Declaration": +// 递归访问 node.data 的 Decl 子结构或跨度。 walkDecl(node.data, visitor); break; } } +// 上层 walker 或宿主调用,先通知 Binding 回调,再按 ASDL 顺序访问子结构;不修改树。 export function walkBinding(node: Binding, visitor: Visitor): void { +// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。 if (visitor.visitBinding?.(node) === false) return; +// 递归访问 node.name 的 Name 子结构或跨度。 walkName(node.name, visitor); +// 递归访问 node.value 的 Expr 子结构或跨度。 walkExpr(node.value, visitor); +// 递归访问 node.span 的 span 子结构或跨度。 walkSpan(node.span, visitor); } +// 上层 walker 或宿主调用,先通知 Assignment 回调,再按 ASDL 顺序访问子结构;不修改树。 export function walkAssignment(node: Assignment, visitor: Visitor): void { +// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。 if (visitor.visitAssignment?.(node) === false) return; +// 递归访问 node.name 的 Name 子结构或跨度。 walkName(node.name, visitor); +// 递归访问 node.value 的 Expr 子结构或跨度。 walkExpr(node.value, visitor); +// 递归访问 node.span 的 span 子结构或跨度。 walkSpan(node.span, visitor); } +// 上层 walker 或宿主调用,先通知 MemberAssignment 回调,再按 ASDL 顺序访问子结构;不修改树。 export function walkMemberAssignment(node: MemberAssignment, visitor: Visitor): void { +// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。 if (visitor.visitMemberAssignment?.(node) === false) return; +// 递归访问 node.object 的 Expr 子结构或跨度。 walkExpr(node.object, visitor); +// 递归访问 node.name 的 Name 子结构或跨度。 walkName(node.name, visitor); +// 递归访问 node.value 的 Expr 子结构或跨度。 walkExpr(node.value, visitor); +// 递归访问 node.span 的 span 子结构或跨度。 walkSpan(node.span, visitor); } +// 上层 walker 或宿主调用,先通知 IfStatement 回调,再按 ASDL 顺序访问子结构;不修改树。 export function walkIfStatement(node: IfStatement, visitor: Visitor): void { +// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。 if (visitor.visitIfStatement?.(node) === false) return; +// 递归访问 node.condition 的 Expr 子结构或跨度。 walkExpr(node.condition, visitor); +// 递归访问 node.body 的 Block 子结构或跨度。 walkBlock(node.body, visitor); +// node.otherwise 可缺省或为 null,只有存在时才访问该子节点。 if (node.otherwise != null) walkBlock(node.otherwise, visitor); +// 递归访问 node.span 的 span 子结构或跨度。 walkSpan(node.span, visitor); } +// 上层 walker 或宿主调用,先通知 WhileStatement 回调,再按 ASDL 顺序访问子结构;不修改树。 export function walkWhileStatement(node: WhileStatement, visitor: Visitor): void { +// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。 if (visitor.visitWhileStatement?.(node) === false) return; +// 递归访问 node.condition 的 Expr 子结构或跨度。 walkExpr(node.condition, visitor); +// 递归访问 node.body 的 Block 子结构或跨度。 walkBlock(node.body, visitor); +// 递归访问 node.span 的 span 子结构或跨度。 walkSpan(node.span, visitor); } +// 上层 walker 或宿主调用,先通知 ReturnStatement 回调,再按 ASDL 顺序访问子结构;不修改树。 export function walkReturnStatement(node: ReturnStatement, visitor: Visitor): void { +// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。 if (visitor.visitReturnStatement?.(node) === false) return; +// node.value 可缺省或为 null,只有存在时才访问该子节点。 if (node.value != null) walkExpr(node.value, visitor); +// 递归访问 node.span 的 span 子结构或跨度。 walkSpan(node.span, visitor); } +// 上层 walker 或宿主调用,先通知 InteractiveInput 回调,再按 ASDL 顺序访问子结构;不修改树。 export function walkInteractiveInput(node: InteractiveInput, visitor: Visitor): void { +// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。 if (visitor.visitInteractiveInput?.(node) === false) return; +// 按声明顺序访问 node.statements 列表中的每个 Stmt 子节点。 for (const value of node.statements) walkStmt(value, visitor); +// 递归访问 node.span 的 span 子结构或跨度。 walkSpan(node.span, visitor); } +// 上层 walker 或宿主调用,先通知 Expr 回调,再按 ASDL 顺序访问子结构;不修改树。 export function walkExpr(node: Expr, visitor: Visitor): void { +// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。 if (visitor.visitExpr?.(node) === false) return; +// 递归访问 node.kind 的 ExprKind 子结构或跨度。 walkExprKind(node.kind, visitor); +// 递归访问 node.span 的 span 子结构或跨度。 walkSpan(node.span, visitor); } +// 上层 walker 或宿主调用,先通知 ExprKind 回调,再按 ASDL 顺序访问子结构;不修改树。 export function walkExprKind(node: ExprKind, visitor: Visitor): void { +// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。 if (visitor.visitExprKind?.(node) === false) return; +// 判别 kind 只选择当前载荷,普通文本/数字拼写没有进一步语法子结构。 switch (node.kind) { +// 变量引用文本,compiler 决定局部槽或全局索引。 case "Name": break; +// 原始十进制拼写,保留全部位数、前导零及尾零,不转浮点。 case "Number": break; +// 已解码受支持转义的拥有文本,原引号范围在外层 Expr。 case "String": break; +// 点开头的符号名,前端可表示,当前 compiler 拒绝生成运行时值。 case "Symbol": +// 递归访问 node.data 的 Name 子结构或跨度。 walkName(node.data, visitor); break; +// 等待子表达式,VM 执行上下文决定是否允许停放。 case "Await": +// 递归访问 node.data 的 Expr 子结构或跨度。 walkExpr(node.data, visitor); break; +// 一元取负,可在纯数值链上合并符号。 case "Unary": +// 递归访问 node.data.operation 的 UnaryOp 子结构或跨度。 walkUnaryOp(node.data.operation, visitor); +// 递归访问 node.data.operand 的 Expr 子结构或跨度。 walkExpr(node.data.operand, visitor); break; +// 左右操作数及算术/比较种类,保留解析优先级树。 case "Binary": +// 递归访问 node.data.operation 的 BinaryOp 子结构或跨度。 walkBinaryOp(node.data.operation, visitor); +// 递归访问 node.data.left 的 Expr 子结构或跨度。 walkExpr(node.data.left, visitor); +// 递归访问 node.data.right 的 Expr 子结构或跨度。 walkExpr(node.data.right, visitor); break; +// 接收对象及属性名的读取。 case "Member": +// 递归访问 node.data.object 的 Expr 子结构或跨度。 walkExpr(node.data.object, visitor); +// 递归访问 node.data.name 的 Name 子结构或跨度。 walkName(node.data.name, visitor); break; +// 被调用表达式与位置实参,由 VM 实际求值。 case "Call": +// 递归访问 node.data.callee 的 Expr 子结构或跨度。 walkExpr(node.data.callee, visitor); +// 按声明顺序访问 node.data.arguments 列表中的每个 Expr 子节点。 for (const value of node.data.arguments) walkExpr(value, visitor); break; } } +// 上层 walker 或宿主调用,先通知 UnaryOp 回调,再按 ASDL 顺序访问子结构;不修改树。 export function walkUnaryOp(node: UnaryOp, visitor: Visitor): void { +// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。 if (visitor.visitUnaryOp?.(node) === false) return; } +// 上层 walker 或宿主调用,先通知 BinaryOp 回调,再按 ASDL 顺序访问子结构;不修改树。 export function walkBinaryOp(node: BinaryOp, visitor: Visitor): void { +// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。 if (visitor.visitBinaryOp?.(node) === false) return; } diff --git a/crates/frontend/src/ast/generated.rs b/crates/frontend/src/ast/generated.rs index c6c4a38..6ad186e 100644 --- a/crates/frontend/src/ast/generated.rs +++ b/crates/frontend/src/ast/generated.rs @@ -2,23 +2,33 @@ use super::Spanned; use crate::Span; +/// wire/编辑器核对的协议身份,来自 ASDL 原文哈希;生成节点注释不参与此值。 pub const AST_SCHEMA_ID: &str = "urn:endor:ast:sha256:21b0349ac295d9bc8cedcad8f07d8e2d45cf220e19e7c2fad10269555fcfd71a"; +/// 宿主/编辑器传输的版本化语法文档,wire 校验后供 compiler 使用。 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct AstDocument { + /// 协议身份,wire 按 AST_SCHEMA_ID 核对;不是源码内容哈希。 pub schema: String, + /// 拥有的语法根,文档校验及编译从这里递归。 pub root: AstRoot, } +/// parse_ast 按模式构造的模块、表达式或交互根,compiler 据此选择生成入口。 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(tag = "kind", content = "data", deny_unknown_fields)] pub enum AstRoot { + /// 模块根,包含函数/类声明。 Module(Program), + /// 表达式根,编译后直接返回求值结果。 Expression(Expr), + /// 交互根,包含一次完整提交。 Interactive(InteractiveInput), } impl Spanned for AstRoot { + /// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。 fn span(&self) -> Span { + // 按实际构造器取得唯一可定位子节点的范围。 match self { Self::Module(node) => node.span(), Self::Expression(node) => node.span(), @@ -26,129 +36,184 @@ impl Spanned for AstRoot { } } } +/// parser 复制的名称文本与位置,用于声明、类型注解或属性名。 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct Name { + /// 拥有名称文本,不依赖输入缓冲存活。 pub text: String, + /// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 pub span: Span, } impl Spanned for Name { + /// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。 fn span(&self) -> Span { self.span } } +/// 模块声明序列,compiler 先注册全部顶层名称再分析各函数体。 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct Program { + /// 按源码顺序的声明;源码模块解析要求非空,文档结构本身不限制数量。 pub declarations: Vec, + /// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 pub span: Span, } impl Spanned for Program { + /// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。 fn span(&self) -> Span { self.span } } +/// 顶层/交互声明的种类及完整范围,compiler 按函数或类分派。 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct Decl { + /// 函数/类声明载荷,JSON 和类型采用 kind/data 标签。 pub kind: DeclKind, + /// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 pub span: Span, } impl Spanned for Decl { + /// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。 fn span(&self) -> Span { self.span } } +/// 函数与类声明的判别载荷,外层 Decl 另外保存完整跨度。 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(tag = "kind", content = "data", deny_unknown_fields)] pub enum DeclKind { + /// 显式参数/返回注解与函数体,compiler 生成独立模板及源码签名;解析本身不调用函数。 Function(Function), + /// 类名、可选基类、字段和方法,compiler 检查成员后生成构类指令。 Class(Class), } impl Spanned for DeclKind { + /// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。 fn span(&self) -> Span { + // 按实际构造器取得唯一可定位子节点的范围。 match self { Self::Function(node) => node.span(), Self::Class(node) => node.span(), } } } +/// 类名、可选基类、字段和方法,compiler 检查成员后生成构类指令。 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct Class { + /// 类声明名与精确位置。 pub name: Name, + /// 显式基类名,可省略;当前 compiler 对省略项使用 Object。 pub base: Option, + /// 按出现顺序的字段名称/类型注解,不含方法。 pub fields: Vec, + /// 按出现顺序的方法;显式 self 约束由 compiler 检查。 pub methods: Vec, + /// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 pub span: Span, } impl Spanned for Class { + /// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。 fn span(&self) -> Span { self.span } } +/// 显式参数/返回注解与函数体,compiler 生成独立模板及源码签名;解析本身不调用函数。 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct Function { + /// 函数/方法名,代码模板沿用为诊断名称。 pub name: Name, + /// 位置参数列表,compiler 依次分配前部局部槽。 pub parameters: Vec, + /// 返回类型名,生成源码签名后由运行时解析。 pub return_type: Name, + /// 独立函数作用域的语句体,当前源码不支持嵌套声明闭包。 pub body: Block, + /// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 pub span: Span, } impl Spanned for Function { + /// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。 fn span(&self) -> Span { self.span } } +/// 函数参数或类字段共用的名称/类型注解,实际类型身份由运行时环境解析。 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct Parameter { + /// 参数/字段名,用于布局和重复检查。 pub name: Name, + /// 声明类型名,前端只保留文本与位置。 pub type_name: Name, + /// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 pub span: Span, } impl Spanned for Parameter { + /// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。 fn span(&self) -> Span { self.span } } +/// 顺序语句块,compiler 检查确定初始化并按语句生成指令。 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct Block { + /// 有序语句列表,不引入独立块级局部作用域。 pub statements: Vec, + /// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 pub span: Span, } impl Spanned for Block { + /// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。 fn span(&self) -> Span { self.span } } +/// 语句种类及原文范围,compiler 处理副作用、控制流或返回。 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct Stmt { + /// 具体语句载荷,外层另存整句范围。 pub kind: StmtKind, + /// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 pub span: Span, } impl Spanned for Stmt { + /// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。 fn span(&self) -> Span { self.span } } +/// 语句判别载荷,parser 选择分支,compiler 按绑定、赋值、控制流等规则处理。 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(tag = "kind", content = "data", deny_unknown_fields)] pub enum StmtKind { + /// let 新绑定,compiler 检查重名并分析右值后才标记目标已初始化。 Binding(Binding), + /// 普通名称赋值,compiler 按作用域写局部槽或全局环境。 Assignment(Assignment), + /// 成员写入的对象、属性名与右值,compiler 生成 StoreAttr。 MemberAssignment(MemberAssignment), + /// 条件、then 和可选 else,compiler 合并继续路径的初始化集合并生成跳转。 If(IfStatement), + /// 循环条件与体,compiler 生成回边并处理循环可能零次执行。 While(WhileStatement), + /// 函数返回,compiler 对缺省值补 Nil;当前源码语法要求 return 后有表达式。 Return(ReturnStatement), + /// 表达式载荷及完整范围,compiler 生成留下一个结果的求值序列。 Expression(Expr), + /// 顶层/交互声明的种类及完整范围,compiler 按函数或类分派。 Declaration(Decl), } impl Spanned for StmtKind { + /// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。 fn span(&self) -> Span { + // 按实际构造器取得唯一可定位子节点的范围。 match self { Self::Binding(node) => node.span(), Self::Assignment(node) => node.span(), @@ -161,141 +226,212 @@ impl Spanned for StmtKind { } } } +/// let 新绑定,compiler 检查重名并分析右值后才标记目标已初始化。 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct Binding { + /// let 目标名,重复声明由语义分析拒绝。 pub name: Name, + /// 绑定右值,本局部不能在初始化前读取自身。 pub value: Expr, + /// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 pub span: Span, } impl Spanned for Binding { + /// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。 fn span(&self) -> Span { self.span } } +/// 普通名称赋值,compiler 按作用域写局部槽或全局环境。 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct Assignment { + /// 普通赋值目标,函数内首次赋值可预分配局部槽。 pub name: Name, + /// 先求右值再更新目标。 pub value: Expr, + /// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 pub span: Span, } impl Spanned for Assignment { + /// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。 fn span(&self) -> Span { self.span } } +/// 成员写入的对象、属性名与右值,compiler 生成 StoreAttr。 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct MemberAssignment { + /// 属性写入接收对象表达式,前端不求值。 pub object: Expr, + /// 待写属性名,compiler 加入名字表。 pub name: Name, + /// 待写右值,当前 compiler 先于接收对象生成求值以匹配 VM 栈布局。 pub value: Expr, + /// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 pub span: Span, } impl Spanned for MemberAssignment { + /// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。 fn span(&self) -> Span { self.span } } +/// 条件、then 和可选 else,compiler 合并继续路径的初始化集合并生成跳转。 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct IfStatement { + /// 条件表达式,运行时按真值协议选择分支。 pub condition: Expr, + /// 条件为真的 then 块。 pub body: Block, + /// 可选 else 块,缺省时存在直接继续路径。 pub otherwise: Option, + /// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 pub span: Span, } impl Spanned for IfStatement { + /// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。 fn span(&self) -> Span { self.span } } +/// 循环条件与体,compiler 生成回边并处理循环可能零次执行。 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct WhileStatement { + /// 每轮重新求值的条件,假值退出。 pub condition: Expr, + /// 循环体,可能一次也不执行。 pub body: Block, + /// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 pub span: Span, } impl Spanned for WhileStatement { + /// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。 fn span(&self) -> Span { self.span } } +/// 函数返回,compiler 对缺省值补 Nil;当前源码语法要求 return 后有表达式。 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct ReturnStatement { + /// 可选返回表达式,手工 AST 缺省时由 compiler 补 Nil。 pub value: Option, + /// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 pub span: Span, } impl Spanned for ReturnStatement { + /// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。 fn span(&self) -> Span { self.span } } +/// 单次交互提交容器,parser 产生一条语句,compiler 也检查数量,不保存宿主会话环境。 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct InteractiveInput { + /// 当前 parse/compile 约定恰好一个完整提交的语句。 pub statements: Vec, + /// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 pub span: Span, } impl Spanned for InteractiveInput { + /// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。 fn span(&self) -> Span { self.span } } +/// 表达式载荷及完整范围,compiler 生成留下一个结果的求值序列。 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct Expr { + /// 求值形状,不缓存名称解析或运行时类型。 pub kind: ExprKind, + /// 原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。 pub span: Span, } impl Spanned for Expr { + /// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。 fn span(&self) -> Span { self.span } } +/// 名称、字面量、等待、运算和调用等表达式载荷,范围由外层 Expr 提供。 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(tag = "kind", content = "data", deny_unknown_fields)] pub enum ExprKind { + /// 变量引用文本,compiler 决定局部槽或全局索引。 Name(String), + /// 原始十进制拼写,保留全部位数、前导零及尾零,不转浮点。 Number(String), + /// 已解码受支持转义的拥有文本,原引号范围在外层 Expr。 String(String), + /// 点开头的符号名,前端可表示,当前 compiler 拒绝生成运行时值。 Symbol(Name), + /// 等待子表达式,VM 执行上下文决定是否允许停放。 Await(Box), + /// 一元取负,可在纯数值链上合并符号。 Unary { + /// 运算种类,决定算术、比较或取负指令。 operation: UnaryOp, + /// 一元子表达式,Rust 的递归回边间接存储。 operand: Box, }, + /// 左右操作数及算术/比较种类,保留解析优先级树。 Binary { + /// 运算种类,决定算术、比较或取负指令。 operation: BinaryOp, + /// 二元左操作数,当前 compiler 先于右侧求值。 left: Box, + /// 二元右操作数,与左侧一起被运行时运算消费。 right: Box, }, + /// 接收对象及属性名的读取。 Member { + /// 成员读取接收对象。 object: Box, + /// 成员或符号名,不是已解析的运行时属性句柄。 name: Name, }, + /// 被调用表达式与位置实参,由 VM 实际求值。 Call { + /// 调用目标,先于实参求值。 callee: Box, + /// 有序位置实参,AST 不隐式添加接收者。 arguments: Vec, }, } +/// 一元运算种类,compiler 合并纯数值负号或生成运行时取负。 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Copy)] pub enum UnaryOp { + /// 一元取负。 Negate, } +/// 二元算术/比较种类,parser 保留优先级树,compiler 映射为对应 VM 操作。 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Copy)] pub enum BinaryOp { + /// 加法。 Add, + /// 减法。 Subtract, + /// 乘法。 Multiply, + /// 除法。 Divide, + /// 相等比较。 Equal, + /// 不等比较。 NotEqual, + /// 小于比较。 Less, + /// 小于等于比较。 LessEqual, + /// 大于比较。 Greater, + /// 大于等于比较。 GreaterEqual, } diff --git a/crates/frontend/src/ast/visitor.rs b/crates/frontend/src/ast/visitor.rs index c8bd1ea..3ba8f16 100644 --- a/crates/frontend/src/ast/visitor.rs +++ b/crates/frontend/src/ast/visitor.rs @@ -6,382 +6,566 @@ use super::{ ReturnStatement, Spanned, Stmt, StmtKind, UnaryOp, WhileStatement, }; use crate::Span; +/// wire 校验与宿主分析实现的借用节点访问协议;默认按 ASDL 字段顺序递归,不修改树。 +/// 覆盖 visit 方法后可显式调用对应 walk 继续子遍历;walk 本身不执行当前节点的进入/退出钩子。 pub trait Visitor { + /// 实现者选择的错误类型,任一子访问失败即向上返回。 type Error; + /// 默认可定位 visit 在遍历前调用;失败时本节点尚未成功进入,不再调用它的 leave_node。 fn enter_node(&mut self, _span: Span) -> Result<(), Self::Error> { Ok(()) } + /// 已成功进入节点的遍历结束后调用,子节点错误也需退出;默认无操作。 fn leave_node(&mut self) {} + /// walk 遇到显式 span 字段时调用,默认不处理;区别于围绕整个节点的 enter_node。 fn visit_span(&mut self, _span: &Span) -> Result<(), Self::Error> { Ok(()) } + /// 上层 walk 或宿主调用的 AstDocument 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。 fn visit_ast_document(&mut self, node: &AstDocument) -> Result<(), Self::Error> { walk_ast_document(self, node) } + /// 上层 walk 或宿主调用的 AstRoot 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。 fn visit_ast_root(&mut self, node: &AstRoot) -> Result<(), Self::Error> { + // 先进入;进入失败则不启动子遍历。 self.enter_node(node.span())?; + // 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。 let result = walk_ast_root(self, node); self.leave_node(); result } + /// 上层 walk 或宿主调用的 Name 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。 fn visit_name(&mut self, node: &Name) -> Result<(), Self::Error> { + // 先进入;进入失败则不启动子遍历。 self.enter_node(node.span())?; + // 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。 let result = walk_name(self, node); self.leave_node(); result } + /// 上层 walk 或宿主调用的 Program 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。 fn visit_program(&mut self, node: &Program) -> Result<(), Self::Error> { + // 先进入;进入失败则不启动子遍历。 self.enter_node(node.span())?; + // 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。 let result = walk_program(self, node); self.leave_node(); result } + /// 上层 walk 或宿主调用的 Decl 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。 fn visit_decl(&mut self, node: &Decl) -> Result<(), Self::Error> { + // 先进入;进入失败则不启动子遍历。 self.enter_node(node.span())?; + // 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。 let result = walk_decl(self, node); self.leave_node(); result } + /// 上层 walk 或宿主调用的 DeclKind 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。 fn visit_decl_kind(&mut self, node: &DeclKind) -> Result<(), Self::Error> { + // 先进入;进入失败则不启动子遍历。 self.enter_node(node.span())?; + // 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。 let result = walk_decl_kind(self, node); self.leave_node(); result } + /// 上层 walk 或宿主调用的 Class 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。 fn visit_class(&mut self, node: &Class) -> Result<(), Self::Error> { + // 先进入;进入失败则不启动子遍历。 self.enter_node(node.span())?; + // 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。 let result = walk_class(self, node); self.leave_node(); result } + /// 上层 walk 或宿主调用的 Function 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。 fn visit_function(&mut self, node: &Function) -> Result<(), Self::Error> { + // 先进入;进入失败则不启动子遍历。 self.enter_node(node.span())?; + // 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。 let result = walk_function(self, node); self.leave_node(); result } + /// 上层 walk 或宿主调用的 Parameter 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。 fn visit_parameter(&mut self, node: &Parameter) -> Result<(), Self::Error> { + // 先进入;进入失败则不启动子遍历。 self.enter_node(node.span())?; + // 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。 let result = walk_parameter(self, node); self.leave_node(); result } + /// 上层 walk 或宿主调用的 Block 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。 fn visit_block(&mut self, node: &Block) -> Result<(), Self::Error> { + // 先进入;进入失败则不启动子遍历。 self.enter_node(node.span())?; + // 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。 let result = walk_block(self, node); self.leave_node(); result } + /// 上层 walk 或宿主调用的 Stmt 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。 fn visit_stmt(&mut self, node: &Stmt) -> Result<(), Self::Error> { + // 先进入;进入失败则不启动子遍历。 self.enter_node(node.span())?; + // 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。 let result = walk_stmt(self, node); self.leave_node(); result } + /// 上层 walk 或宿主调用的 StmtKind 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。 fn visit_stmt_kind(&mut self, node: &StmtKind) -> Result<(), Self::Error> { + // 先进入;进入失败则不启动子遍历。 self.enter_node(node.span())?; + // 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。 let result = walk_stmt_kind(self, node); self.leave_node(); result } + /// 上层 walk 或宿主调用的 Binding 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。 fn visit_binding(&mut self, node: &Binding) -> Result<(), Self::Error> { + // 先进入;进入失败则不启动子遍历。 self.enter_node(node.span())?; + // 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。 let result = walk_binding(self, node); self.leave_node(); result } + /// 上层 walk 或宿主调用的 Assignment 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。 fn visit_assignment(&mut self, node: &Assignment) -> Result<(), Self::Error> { + // 先进入;进入失败则不启动子遍历。 self.enter_node(node.span())?; + // 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。 let result = walk_assignment(self, node); self.leave_node(); result } + /// 上层 walk 或宿主调用的 MemberAssignment 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。 fn visit_member_assignment(&mut self, node: &MemberAssignment) -> Result<(), Self::Error> { + // 先进入;进入失败则不启动子遍历。 self.enter_node(node.span())?; + // 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。 let result = walk_member_assignment(self, node); self.leave_node(); result } + /// 上层 walk 或宿主调用的 IfStatement 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。 fn visit_if_statement(&mut self, node: &IfStatement) -> Result<(), Self::Error> { + // 先进入;进入失败则不启动子遍历。 self.enter_node(node.span())?; + // 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。 let result = walk_if_statement(self, node); self.leave_node(); result } + /// 上层 walk 或宿主调用的 WhileStatement 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。 fn visit_while_statement(&mut self, node: &WhileStatement) -> Result<(), Self::Error> { + // 先进入;进入失败则不启动子遍历。 self.enter_node(node.span())?; + // 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。 let result = walk_while_statement(self, node); self.leave_node(); result } + /// 上层 walk 或宿主调用的 ReturnStatement 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。 fn visit_return_statement(&mut self, node: &ReturnStatement) -> Result<(), Self::Error> { + // 先进入;进入失败则不启动子遍历。 self.enter_node(node.span())?; + // 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。 let result = walk_return_statement(self, node); self.leave_node(); result } + /// 上层 walk 或宿主调用的 InteractiveInput 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。 fn visit_interactive_input(&mut self, node: &InteractiveInput) -> Result<(), Self::Error> { + // 先进入;进入失败则不启动子遍历。 self.enter_node(node.span())?; + // 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。 let result = walk_interactive_input(self, node); self.leave_node(); result } + /// 上层 walk 或宿主调用的 Expr 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。 fn visit_expr(&mut self, node: &Expr) -> Result<(), Self::Error> { + // 先进入;进入失败则不启动子遍历。 self.enter_node(node.span())?; + // 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。 let result = walk_expr(self, node); self.leave_node(); result } + /// 上层 walk 或宿主调用的 ExprKind 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。 fn visit_expr_kind(&mut self, node: &ExprKind) -> Result<(), Self::Error> { walk_expr_kind(self, node) } + /// 上层 walk 或宿主调用的 UnaryOp 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。 fn visit_unary_op(&mut self, node: &UnaryOp) -> Result<(), Self::Error> { walk_unary_op(self, node) } + /// 上层 walk 或宿主调用的 BinaryOp 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。 fn visit_binary_op(&mut self, node: &BinaryOp) -> Result<(), Self::Error> { walk_binary_op(self, node) } } +/// 默认 visit_ast_document 或自定义访问器调用,按字段顺序借用 AstDocument 的子结构;本函数不进入/退出当前节点。 pub fn walk_ast_document( visitor: &mut V, node: &AstDocument, ) -> Result<(), V::Error> { + // 访问 &node.root 的 AstRoot 子结构或跨度,借用不转移节点所有权。 visitor.visit_ast_root(&node.root)?; Ok(()) } +/// 默认 visit_ast_root 或自定义访问器调用,按字段顺序借用 AstRoot 的子结构;本函数不进入/退出当前节点。 pub fn walk_ast_root(visitor: &mut V, node: &AstRoot) -> Result<(), V::Error> { + // 按构造器只递归当前实际载荷,字符串等标量没有语法子节点。 match node { + // 模块根,包含函数/类声明。 AstRoot::Module(value) => { + // 访问 value 的 Program 子结构或跨度,借用不转移节点所有权。 visitor.visit_program(value)?; } + // 表达式根,编译后直接返回求值结果。 AstRoot::Expression(value) => { + // 访问 value 的 Expr 子结构或跨度,借用不转移节点所有权。 visitor.visit_expr(value)?; } + // 交互根,包含一次完整提交。 AstRoot::Interactive(value) => { + // 访问 value 的 InteractiveInput 子结构或跨度,借用不转移节点所有权。 visitor.visit_interactive_input(value)?; } } Ok(()) } +/// 默认 visit_name 或自定义访问器调用,按字段顺序借用 Name 的子结构;本函数不进入/退出当前节点。 pub fn walk_name(visitor: &mut V, node: &Name) -> Result<(), V::Error> { + // 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。 visitor.visit_span(&node.span)?; Ok(()) } +/// 默认 visit_program 或自定义访问器调用,按字段顺序借用 Program 的子结构;本函数不进入/退出当前节点。 pub fn walk_program(visitor: &mut V, node: &Program) -> Result<(), V::Error> { + // 按顺序借用 &node.declarations 中每个 Decl,任一访问失败立即向上传播。 for value in &node.declarations { visitor.visit_decl(value)?; } + // 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。 visitor.visit_span(&node.span)?; Ok(()) } +/// 默认 visit_decl 或自定义访问器调用,按字段顺序借用 Decl 的子结构;本函数不进入/退出当前节点。 pub fn walk_decl(visitor: &mut V, node: &Decl) -> Result<(), V::Error> { + // 访问 &node.kind 的 DeclKind 子结构或跨度,借用不转移节点所有权。 visitor.visit_decl_kind(&node.kind)?; + // 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。 visitor.visit_span(&node.span)?; Ok(()) } +/// 默认 visit_decl_kind 或自定义访问器调用,按字段顺序借用 DeclKind 的子结构;本函数不进入/退出当前节点。 pub fn walk_decl_kind( visitor: &mut V, node: &DeclKind, ) -> Result<(), V::Error> { + // 按构造器只递归当前实际载荷,字符串等标量没有语法子节点。 match node { + // 显式参数/返回注解与函数体,compiler 生成独立模板及源码签名;解析本身不调用函数。 DeclKind::Function(value) => { + // 访问 value 的 Function 子结构或跨度,借用不转移节点所有权。 visitor.visit_function(value)?; } + // 类名、可选基类、字段和方法,compiler 检查成员后生成构类指令。 DeclKind::Class(value) => { + // 访问 value 的 Class 子结构或跨度,借用不转移节点所有权。 visitor.visit_class(value)?; } } Ok(()) } +/// 默认 visit_class 或自定义访问器调用,按字段顺序借用 Class 的子结构;本函数不进入/退出当前节点。 pub fn walk_class(visitor: &mut V, node: &Class) -> Result<(), V::Error> { + // 访问 &node.name 的 Name 子结构或跨度,借用不转移节点所有权。 visitor.visit_name(&node.name)?; + // &node.base 是可选字段,仅 Some 时访问其 Name 子节点。 if let Some(value) = &node.base { visitor.visit_name(value)?; } + // 按顺序借用 &node.fields 中每个 Parameter,任一访问失败立即向上传播。 for value in &node.fields { visitor.visit_parameter(value)?; } + // 按顺序借用 &node.methods 中每个 Function,任一访问失败立即向上传播。 for value in &node.methods { visitor.visit_function(value)?; } + // 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。 visitor.visit_span(&node.span)?; Ok(()) } +/// 默认 visit_function 或自定义访问器调用,按字段顺序借用 Function 的子结构;本函数不进入/退出当前节点。 pub fn walk_function( visitor: &mut V, node: &Function, ) -> Result<(), V::Error> { + // 访问 &node.name 的 Name 子结构或跨度,借用不转移节点所有权。 visitor.visit_name(&node.name)?; + // 按顺序借用 &node.parameters 中每个 Parameter,任一访问失败立即向上传播。 for value in &node.parameters { visitor.visit_parameter(value)?; } + // 访问 &node.return_type 的 Name 子结构或跨度,借用不转移节点所有权。 visitor.visit_name(&node.return_type)?; + // 访问 &node.body 的 Block 子结构或跨度,借用不转移节点所有权。 visitor.visit_block(&node.body)?; + // 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。 visitor.visit_span(&node.span)?; Ok(()) } +/// 默认 visit_parameter 或自定义访问器调用,按字段顺序借用 Parameter 的子结构;本函数不进入/退出当前节点。 pub fn walk_parameter( visitor: &mut V, node: &Parameter, ) -> Result<(), V::Error> { + // 访问 &node.name 的 Name 子结构或跨度,借用不转移节点所有权。 visitor.visit_name(&node.name)?; + // 访问 &node.type_name 的 Name 子结构或跨度,借用不转移节点所有权。 visitor.visit_name(&node.type_name)?; + // 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。 visitor.visit_span(&node.span)?; Ok(()) } +/// 默认 visit_block 或自定义访问器调用,按字段顺序借用 Block 的子结构;本函数不进入/退出当前节点。 pub fn walk_block(visitor: &mut V, node: &Block) -> Result<(), V::Error> { + // 按顺序借用 &node.statements 中每个 Stmt,任一访问失败立即向上传播。 for value in &node.statements { visitor.visit_stmt(value)?; } + // 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。 visitor.visit_span(&node.span)?; Ok(()) } +/// 默认 visit_stmt 或自定义访问器调用,按字段顺序借用 Stmt 的子结构;本函数不进入/退出当前节点。 pub fn walk_stmt(visitor: &mut V, node: &Stmt) -> Result<(), V::Error> { + // 访问 &node.kind 的 StmtKind 子结构或跨度,借用不转移节点所有权。 visitor.visit_stmt_kind(&node.kind)?; + // 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。 visitor.visit_span(&node.span)?; Ok(()) } +/// 默认 visit_stmt_kind 或自定义访问器调用,按字段顺序借用 StmtKind 的子结构;本函数不进入/退出当前节点。 pub fn walk_stmt_kind( visitor: &mut V, node: &StmtKind, ) -> Result<(), V::Error> { + // 按构造器只递归当前实际载荷,字符串等标量没有语法子节点。 match node { + // let 新绑定,compiler 检查重名并分析右值后才标记目标已初始化。 StmtKind::Binding(value) => { + // 访问 value 的 Binding 子结构或跨度,借用不转移节点所有权。 visitor.visit_binding(value)?; } + // 普通名称赋值,compiler 按作用域写局部槽或全局环境。 StmtKind::Assignment(value) => { + // 访问 value 的 Assignment 子结构或跨度,借用不转移节点所有权。 visitor.visit_assignment(value)?; } + // 成员写入的对象、属性名与右值,compiler 生成 StoreAttr。 StmtKind::MemberAssignment(value) => { + // 访问 value 的 MemberAssignment 子结构或跨度,借用不转移节点所有权。 visitor.visit_member_assignment(value)?; } + // 条件、then 和可选 else,compiler 合并继续路径的初始化集合并生成跳转。 StmtKind::If(value) => { + // 访问 value 的 IfStatement 子结构或跨度,借用不转移节点所有权。 visitor.visit_if_statement(value)?; } + // 循环条件与体,compiler 生成回边并处理循环可能零次执行。 StmtKind::While(value) => { + // 访问 value 的 WhileStatement 子结构或跨度,借用不转移节点所有权。 visitor.visit_while_statement(value)?; } + // 函数返回,compiler 对缺省值补 Nil;当前源码语法要求 return 后有表达式。 StmtKind::Return(value) => { + // 访问 value 的 ReturnStatement 子结构或跨度,借用不转移节点所有权。 visitor.visit_return_statement(value)?; } + // 表达式载荷及完整范围,compiler 生成留下一个结果的求值序列。 StmtKind::Expression(value) => { + // 访问 value 的 Expr 子结构或跨度,借用不转移节点所有权。 visitor.visit_expr(value)?; } + // 顶层/交互声明的种类及完整范围,compiler 按函数或类分派。 StmtKind::Declaration(value) => { + // 访问 value 的 Decl 子结构或跨度,借用不转移节点所有权。 visitor.visit_decl(value)?; } } Ok(()) } +/// 默认 visit_binding 或自定义访问器调用,按字段顺序借用 Binding 的子结构;本函数不进入/退出当前节点。 pub fn walk_binding(visitor: &mut V, node: &Binding) -> Result<(), V::Error> { + // 访问 &node.name 的 Name 子结构或跨度,借用不转移节点所有权。 visitor.visit_name(&node.name)?; + // 访问 &node.value 的 Expr 子结构或跨度,借用不转移节点所有权。 visitor.visit_expr(&node.value)?; + // 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。 visitor.visit_span(&node.span)?; Ok(()) } +/// 默认 visit_assignment 或自定义访问器调用,按字段顺序借用 Assignment 的子结构;本函数不进入/退出当前节点。 pub fn walk_assignment( visitor: &mut V, node: &Assignment, ) -> Result<(), V::Error> { + // 访问 &node.name 的 Name 子结构或跨度,借用不转移节点所有权。 visitor.visit_name(&node.name)?; + // 访问 &node.value 的 Expr 子结构或跨度,借用不转移节点所有权。 visitor.visit_expr(&node.value)?; + // 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。 visitor.visit_span(&node.span)?; Ok(()) } +/// 默认 visit_member_assignment 或自定义访问器调用,按字段顺序借用 MemberAssignment 的子结构;本函数不进入/退出当前节点。 pub fn walk_member_assignment( visitor: &mut V, node: &MemberAssignment, ) -> Result<(), V::Error> { + // 访问 &node.object 的 Expr 子结构或跨度,借用不转移节点所有权。 visitor.visit_expr(&node.object)?; + // 访问 &node.name 的 Name 子结构或跨度,借用不转移节点所有权。 visitor.visit_name(&node.name)?; + // 访问 &node.value 的 Expr 子结构或跨度,借用不转移节点所有权。 visitor.visit_expr(&node.value)?; + // 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。 visitor.visit_span(&node.span)?; Ok(()) } +/// 默认 visit_if_statement 或自定义访问器调用,按字段顺序借用 IfStatement 的子结构;本函数不进入/退出当前节点。 pub fn walk_if_statement( visitor: &mut V, node: &IfStatement, ) -> Result<(), V::Error> { + // 访问 &node.condition 的 Expr 子结构或跨度,借用不转移节点所有权。 visitor.visit_expr(&node.condition)?; + // 访问 &node.body 的 Block 子结构或跨度,借用不转移节点所有权。 visitor.visit_block(&node.body)?; + // &node.otherwise 是可选字段,仅 Some 时访问其 Block 子节点。 if let Some(value) = &node.otherwise { visitor.visit_block(value)?; } + // 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。 visitor.visit_span(&node.span)?; Ok(()) } +/// 默认 visit_while_statement 或自定义访问器调用,按字段顺序借用 WhileStatement 的子结构;本函数不进入/退出当前节点。 pub fn walk_while_statement( visitor: &mut V, node: &WhileStatement, ) -> Result<(), V::Error> { + // 访问 &node.condition 的 Expr 子结构或跨度,借用不转移节点所有权。 visitor.visit_expr(&node.condition)?; + // 访问 &node.body 的 Block 子结构或跨度,借用不转移节点所有权。 visitor.visit_block(&node.body)?; + // 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。 visitor.visit_span(&node.span)?; Ok(()) } +/// 默认 visit_return_statement 或自定义访问器调用,按字段顺序借用 ReturnStatement 的子结构;本函数不进入/退出当前节点。 pub fn walk_return_statement( visitor: &mut V, node: &ReturnStatement, ) -> Result<(), V::Error> { + // &node.value 是可选字段,仅 Some 时访问其 Expr 子节点。 if let Some(value) = &node.value { visitor.visit_expr(value)?; } + // 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。 visitor.visit_span(&node.span)?; Ok(()) } +/// 默认 visit_interactive_input 或自定义访问器调用,按字段顺序借用 InteractiveInput 的子结构;本函数不进入/退出当前节点。 pub fn walk_interactive_input( visitor: &mut V, node: &InteractiveInput, ) -> Result<(), V::Error> { + // 按顺序借用 &node.statements 中每个 Stmt,任一访问失败立即向上传播。 for value in &node.statements { visitor.visit_stmt(value)?; } + // 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。 visitor.visit_span(&node.span)?; Ok(()) } +/// 默认 visit_expr 或自定义访问器调用,按字段顺序借用 Expr 的子结构;本函数不进入/退出当前节点。 pub fn walk_expr(visitor: &mut V, node: &Expr) -> Result<(), V::Error> { + // 访问 &node.kind 的 ExprKind 子结构或跨度,借用不转移节点所有权。 visitor.visit_expr_kind(&node.kind)?; + // 访问 &node.span 的 span 子结构或跨度,借用不转移节点所有权。 visitor.visit_span(&node.span)?; Ok(()) } +/// 默认 visit_expr_kind 或自定义访问器调用,按字段顺序借用 ExprKind 的子结构;本函数不进入/退出当前节点。 pub fn walk_expr_kind( visitor: &mut V, node: &ExprKind, ) -> Result<(), V::Error> { + // 按构造器只递归当前实际载荷,字符串等标量没有语法子节点。 match node { + // 变量引用文本,compiler 决定局部槽或全局索引。 ExprKind::Name(_) => {} + // 原始十进制拼写,保留全部位数、前导零及尾零,不转浮点。 ExprKind::Number(_) => {} + // 已解码受支持转义的拥有文本,原引号范围在外层 Expr。 ExprKind::String(_) => {} + // 点开头的符号名,前端可表示,当前 compiler 拒绝生成运行时值。 ExprKind::Symbol(value) => { + // 访问 value 的 Name 子结构或跨度,借用不转移节点所有权。 visitor.visit_name(value)?; } + // 等待子表达式,VM 执行上下文决定是否允许停放。 ExprKind::Await(value) => { + // 访问 value 的 Expr 子结构或跨度,借用不转移节点所有权。 visitor.visit_expr(value)?; } + // 一元取负,可在纯数值链上合并符号。 ExprKind::Unary { operation, operand, .. } => { + // 访问 operation 的 UnaryOp 子结构或跨度,借用不转移节点所有权。 visitor.visit_unary_op(operation)?; + // 访问 operand 的 Expr 子结构或跨度,借用不转移节点所有权。 visitor.visit_expr(operand)?; } + // 左右操作数及算术/比较种类,保留解析优先级树。 ExprKind::Binary { operation, left, right, .. } => { + // 访问 operation 的 BinaryOp 子结构或跨度,借用不转移节点所有权。 visitor.visit_binary_op(operation)?; + // 访问 left 的 Expr 子结构或跨度,借用不转移节点所有权。 visitor.visit_expr(left)?; + // 访问 right 的 Expr 子结构或跨度,借用不转移节点所有权。 visitor.visit_expr(right)?; } + // 接收对象及属性名的读取。 ExprKind::Member { object, name, .. } => { + // 访问 object 的 Expr 子结构或跨度,借用不转移节点所有权。 visitor.visit_expr(object)?; + // 访问 name 的 Name 子结构或跨度,借用不转移节点所有权。 visitor.visit_name(name)?; } + // 被调用表达式与位置实参,由 VM 实际求值。 ExprKind::Call { callee, arguments, .. } => { + // 访问 callee 的 Expr 子结构或跨度,借用不转移节点所有权。 visitor.visit_expr(callee)?; + // 按顺序借用 arguments 中每个 Expr,任一访问失败立即向上传播。 for value in arguments { visitor.visit_expr(value)?; } @@ -389,12 +573,14 @@ pub fn walk_expr_kind( } Ok(()) } +/// 默认 visit_unary_op 或自定义访问器调用,按字段顺序借用 UnaryOp 的子结构;本函数不进入/退出当前节点。 pub fn walk_unary_op( _visitor: &mut V, _node: &UnaryOp, ) -> Result<(), V::Error> { Ok(()) } +/// 默认 visit_binary_op 或自定义访问器调用,按字段顺序借用 BinaryOp 的子结构;本函数不进入/退出当前节点。 pub fn walk_binary_op( _visitor: &mut V, _node: &BinaryOp, diff --git a/scripts/generate_ast.py b/scripts/generate_ast.py index cce2d9b..54611d7 100644 --- a/scripts/generate_ast.py +++ b/scripts/generate_ast.py @@ -23,6 +23,123 @@ HEADER = "// Generated by scripts/generate_ast.py from Endor.asdl. Do not edit.\ RESERVED = set("as break const continue crate else enum extern false fn for if impl in let loop match mod move mut pub ref return self Self static struct super trait true type unsafe use where while async await dyn abstract become box do final macro override priv typeof unsized virtual yield try gen".split()) +# 说明与 ASDL 分开维护,仅输出源码注释,不改变按 ASDL 原文计算的协议身份。 +TYPE_NOTES = { + "AstDocument": "宿主/编辑器传输的版本化语法文档,wire 校验后供 compiler 使用。", + "AstRoot": "parse_ast 按模式构造的模块、表达式或交互根,compiler 据此选择生成入口。", + "Name": "parser 复制的名称文本与位置,用于声明、类型注解或属性名。", + "Program": "模块声明序列,compiler 先注册全部顶层名称再分析各函数体。", + "Decl": "顶层/交互声明的种类及完整范围,compiler 按函数或类分派。", + "DeclKind": "函数与类声明的判别载荷,外层 Decl 另外保存完整跨度。", + "Class": "类名、可选基类、字段和方法,compiler 检查成员后生成构类指令。", + "Function": "显式参数/返回注解与函数体,compiler 生成独立模板及源码签名;解析本身不调用函数。", + "Parameter": "函数参数或类字段共用的名称/类型注解,实际类型身份由运行时环境解析。", + "Block": "顺序语句块,compiler 检查确定初始化并按语句生成指令。", + "Stmt": "语句种类及原文范围,compiler 处理副作用、控制流或返回。", + "StmtKind": "语句判别载荷,parser 选择分支,compiler 按绑定、赋值、控制流等规则处理。", + "Binding": "let 新绑定,compiler 检查重名并分析右值后才标记目标已初始化。", + "Assignment": "普通名称赋值,compiler 按作用域写局部槽或全局环境。", + "MemberAssignment": "成员写入的对象、属性名与右值,compiler 生成 StoreAttr。", + "IfStatement": "条件、then 和可选 else,compiler 合并继续路径的初始化集合并生成跳转。", + "WhileStatement": "循环条件与体,compiler 生成回边并处理循环可能零次执行。", + "ReturnStatement": "函数返回,compiler 对缺省值补 Nil;当前源码语法要求 return 后有表达式。", + "InteractiveInput": "单次交互提交容器,parser 产生一条语句,compiler 也检查数量,不保存宿主会话环境。", + "Expr": "表达式载荷及完整范围,compiler 生成留下一个结果的求值序列。", + "ExprKind": "名称、字面量、等待、运算和调用等表达式载荷,范围由外层 Expr 提供。", + "UnaryOp": "一元运算种类,compiler 合并纯数值负号或生成运行时取负。", + "BinaryOp": "二元算术/比较种类,parser 保留优先级树,compiler 映射为对应 VM 操作。", +} +# 字段说明以拥有类型和字段名为键,不将不同节点的同名字段混为相同用途。 +FIELD_NOTES = { + ("AstDocument", "schema"): "协议身份,wire 按 AST_SCHEMA_ID 核对;不是源码内容哈希。", + ("AstDocument", "root"): "拥有的语法根,文档校验及编译从这里递归。", + ("Name", "text"): "拥有名称文本,不依赖输入缓冲存活。", + ("Program", "declarations"): "按源码顺序的声明;源码模块解析要求非空,文档结构本身不限制数量。", + ("Decl", "kind"): "函数/类声明载荷,JSON 和类型采用 kind/data 标签。", + ("Class", "name"): "类声明名与精确位置。", + ("Class", "base"): "显式基类名,可省略;当前 compiler 对省略项使用 Object。", + ("Class", "fields"): "按出现顺序的字段名称/类型注解,不含方法。", + ("Class", "methods"): "按出现顺序的方法;显式 self 约束由 compiler 检查。", + ("Function", "name"): "函数/方法名,代码模板沿用为诊断名称。", + ("Function", "parameters"): "位置参数列表,compiler 依次分配前部局部槽。", + ("Function", "return_type"): "返回类型名,生成源码签名后由运行时解析。", + ("Function", "body"): "独立函数作用域的语句体,当前源码不支持嵌套声明闭包。", + ("Parameter", "name"): "参数/字段名,用于布局和重复检查。", + ("Parameter", "type_name"): "声明类型名,前端只保留文本与位置。", + ("Block", "statements"): "有序语句列表,不引入独立块级局部作用域。", + ("Stmt", "kind"): "具体语句载荷,外层另存整句范围。", + ("Binding", "name"): "let 目标名,重复声明由语义分析拒绝。", + ("Binding", "value"): "绑定右值,本局部不能在初始化前读取自身。", + ("Assignment", "name"): "普通赋值目标,函数内首次赋值可预分配局部槽。", + ("Assignment", "value"): "先求右值再更新目标。", + ("MemberAssignment", "object"): "属性写入接收对象表达式,前端不求值。", + ("MemberAssignment", "name"): "待写属性名,compiler 加入名字表。", + ("MemberAssignment", "value"): "待写右值,当前 compiler 先于接收对象生成求值以匹配 VM 栈布局。", + ("IfStatement", "condition"): "条件表达式,运行时按真值协议选择分支。", + ("IfStatement", "body"): "条件为真的 then 块。", + ("IfStatement", "otherwise"): "可选 else 块,缺省时存在直接继续路径。", + ("WhileStatement", "condition"): "每轮重新求值的条件,假值退出。", + ("WhileStatement", "body"): "循环体,可能一次也不执行。", + ("ReturnStatement", "value"): "可选返回表达式,手工 AST 缺省时由 compiler 补 Nil。", + ("InteractiveInput", "statements"): "当前 parse/compile 约定恰好一个完整提交的语句。", + ("Expr", "kind"): "求值形状,不缓存名称解析或运行时类型。", + ("ExprKind", "operation"): "运算种类,决定算术、比较或取负指令。", + ("ExprKind", "operand"): "一元子表达式,Rust 的递归回边间接存储。", + ("ExprKind", "left"): "二元左操作数,当前 compiler 先于右侧求值。", + ("ExprKind", "right"): "二元右操作数,与左侧一起被运行时运算消费。", + ("ExprKind", "object"): "成员读取接收对象。", + ("ExprKind", "name"): "成员或符号名,不是已解析的运行时属性句柄。", + ("ExprKind", "callee"): "调用目标,先于实参求值。", + ("ExprKind", "arguments"): "有序位置实参,AST 不隐式添加接收者。", +} +VARIANT_NOTES = { + ("AstRoot", "Module"): "模块根,包含函数/类声明。", + ("AstRoot", "Expression"): "表达式根,编译后直接返回求值结果。", + ("AstRoot", "Interactive"): "交互根,包含一次完整提交。", + ("ExprKind", "Name"): "变量引用文本,compiler 决定局部槽或全局索引。", + ("ExprKind", "Number"): "原始十进制拼写,保留全部位数、前导零及尾零,不转浮点。", + ("ExprKind", "String"): "已解码受支持转义的拥有文本,原引号范围在外层 Expr。", + ("ExprKind", "Symbol"): "点开头的符号名,前端可表示,当前 compiler 拒绝生成运行时值。", + ("ExprKind", "Await"): "等待子表达式,VM 执行上下文决定是否允许停放。", + ("ExprKind", "Unary"): "一元取负,可在纯数值链上合并符号。", + ("ExprKind", "Binary"): "左右操作数及算术/比较种类,保留解析优先级树。", + ("ExprKind", "Member"): "接收对象及属性名的读取。", + ("ExprKind", "Call"): "被调用表达式与位置实参,由 VM 实际求值。", + ("UnaryOp", "Negate"): "一元取负。", +} + + +# 操作符叶子没有子节点,仍需给各构造器明确语义;扩展项继续使用通用说明。 +VARIANT_NOTES.update({("BinaryOp", name): note for name, note in { + "Add": "加法。", "Subtract": "减法。", "Multiply": "乘法。", "Divide": "除法。", + "Equal": "相等比较。", "NotEqual": "不等比较。", "Less": "小于比较。", + "LessEqual": "小于等于比较。", "Greater": "大于比较。", "GreaterEqual": "大于等于比较。", +}.items()}) + + +# Rust/TS 类型声明生成共用,未记录的扩展节点仍能按结构正常生成。 +def type_note(name): + return TYPE_NOTES.get(name, f"ASDL 定义的 {name} 节点,由宿主构造并由访问器消费。") + + +# 字段说明共用;span 的位置协议在所有节点中保持一致。 +def field_note(owner, field): + # 内置 span 的统一位置协议优先于拥有节点的特定字段含义。 + if field.type == "span": + return "原始 UTF-8 文本的半开字节范围,定位与父子范围校验共用;不是字符列。" + return FIELD_NOTES.get((owner, field.name), f"{field.name} 保存 {field.type} 类型" + ("的有序列表。" if field.quantifier == "*" else "的可选值。" if field.quantifier == "?" else "的值。")) + + +# 和类型说明共用,包装已知节点时复用其语义,否则按构造器形状提供结构说明。 +def variant_note(owner, variant): + # 单载荷包装已知节点时复用节点说明,未知扩展构造器仍保留通用结构说明。 + if len(variant.fields) == 1 and variant.fields[0].type in TYPE_NOTES: + fallback = type_note(variant.fields[0].type) + else: + fallback = f"{owner} 的 {variant.name} 构造器,载荷由 ASDL 字段定义。" + return VARIANT_NOTES.get((owner, variant.name), fallback) + + @dataclass(frozen=True) # 不可变字段描述,由 parse_schema 构造,Rust/TS/JSON 及遍历器生成阶段共同消费。 class Field: @@ -264,10 +381,11 @@ def rust_types(definitions, schema_id): value = f"Box<{value}>" return f"Option<{value}>" if field.quantifier == "?" else value - lines = [HEADER, "use crate::Span;", "use super::Spanned;", f'pub const AST_SCHEMA_ID: &str = "{schema_id}";'] + lines = [HEADER, "use crate::Span;", "use super::Spanned;", "/// wire/编辑器核对的协议身份,来自 ASDL 原文哈希;生成节点注释不参与此值。", f'pub const AST_SCHEMA_ID: &str = "{schema_id}";'] # 依稳定 schema 顺序处理每个定义,保留生成结果的可重复性。 for definition in definitions.values(): # 所有 AST 类型支持拥有副本、相等比较及 serde,简单无载荷枚举才额外 Copy。 + lines.append("/// " + type_note(definition.name)) derives = "Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize" # 无载荷和类型可用简单枚举/字符串联合表示,无需 kind/data 的载荷包装。 if definition.simple(): @@ -281,11 +399,14 @@ def rust_types(definitions, schema_id): # 产品按字段顺序生成;和类型则按构造器分支选择对应形状。 if definition.fields is not None: lines.append(f"pub struct {definition.name} {{") - lines.extend(f"pub {f.name}: {field_type(definition.name, f)}," for f in definition.fields) + for field in definition.fields: + # 注释与字段同序输出,重新生成时一起保留。 + lines += ["/// " + field_note(definition.name, field), f"pub {field.name}: {field_type(definition.name, field)},"] else: lines.append(f"pub enum {definition.name} {{") # 每个构造器按稳定顺序生成,不合并名称相近但身份不同的分支。 for variant in definition.variants: + lines.append("/// " + variant_note(definition.name, variant)) # 无载荷构造器不携带额外字段,单值和多字段载荷分别采用相应表示。 if not variant.fields: lines.append(f"{variant.name},") @@ -294,15 +415,18 @@ def rust_types(definitions, schema_id): lines.append(f"{variant.name}({field_type(definition.name, variant.fields[0])}),") else: # 多字段构造器的具名成员保留每个字段自己的类型/数量修饰。 - fields = ", ".join(f"{f.name}: {field_type(definition.name, f)}" for f in variant.fields) + fields = ",\n".join(f"/// {field_note(definition.name, f)}\n{f.name}: {field_type(definition.name, f)}" for f in variant.fields) lines.append(f"{variant.name} {{ {fields} }},") lines.append("}") # 只为结构上可定位的类型生成 Spanned,不编造 ExprKind 这类混合载荷的范围。 if definition.name in spanned: - lines.append(f"impl Spanned for {definition.name} {{ fn span(&self) -> Span {{") + lines.append(f"impl Spanned for {definition.name} {{") + lines.append("/// 原文切片、定位和 Visitor 节点钩子调用,返回存储范围或被包装节点的范围。") + lines.append("fn span(&self) -> Span {") if definition.fields is not None: lines.append("self.span") else: + lines.append("// 按实际构造器取得唯一可定位子节点的范围。") lines.append("match self {") lines.extend(f"Self::{v.name}(node) => node.span()," for v in definition.variants) lines.append("}") @@ -316,16 +440,23 @@ def rust_visitor(definitions): spanned = spanned_types(definitions) # 按 schema 顺序导入所有节点类型,walk 可以直接引用它们。 names = ", ".join(definitions) - lines = [HEADER, f"use super::{{{names}, Spanned}};", "use crate::Span;", "pub trait Visitor {", "type Error;", + lines = [HEADER, f"use super::{{{names}, Spanned}};", "use crate::Span;", + "/// wire 校验与宿主分析实现的借用节点访问协议;默认按 ASDL 字段顺序递归,不修改树。", + "/// 覆盖 visit 方法后可显式调用对应 walk 继续子遍历;walk 本身不执行当前节点的进入/退出钩子。", + "pub trait Visitor {", "/// 实现者选择的错误类型,任一子访问失败即向上返回。", "type Error;", + "/// 默认可定位 visit 在遍历前调用;失败时本节点尚未成功进入,不再调用它的 leave_node。", "fn enter_node(&mut self, _span: Span) -> Result<(), Self::Error> { Ok(()) }", + "/// 已成功进入节点的遍历结束后调用,子节点错误也需退出;默认无操作。", "fn leave_node(&mut self) {}", + "/// walk 遇到显式 span 字段时调用,默认不处理;区别于围绕整个节点的 enter_node。", "fn visit_span(&mut self, _span: &Span) -> Result<(), Self::Error> { Ok(()) }"] # 按定义顺序遍历/输出每个命名类型,避免由无序集合决定源码结构。 for name in definitions: + lines.append(f"/// 上层 walk 或宿主调用的 {name} 入口;默认继续对应子结构,可覆盖此方法收集/校验节点。") lines.append(f"fn visit_{snake(name)}(&mut self, node: &{name}) -> Result<(), Self::Error> {{") # 可定位节点先进入,再保存子遍历 Result,退出后才返回错误,保证成功进入祖先的钩子配平。 if name in spanned: - lines += ["self.enter_node(node.span())?;", f"let result = walk_{snake(name)}(self, node);", "self.leave_node();", "result"] + lines += ["// 先进入;进入失败则不启动子遍历。", "self.enter_node(node.span())?;", "// 保存子遍历结果,不在此使用 ?,确保失败时仍退出已进入节点。", f"let result = walk_{snake(name)}(self, node);", "self.leave_node();", "result"] else: lines.append(f"walk_{snake(name)}(self, node)") lines.append("}") @@ -340,11 +471,11 @@ def rust_visitor(definitions): method = f"visitor.visit_{snake(field.type)}" # 序列优先映射到 Vec/Array/array;容器本身处理重复元素并打断 Rust 按值递归。 if field.quantifier == "*": - return [f"for value in {access} {{ {method}(value)?; }}"] + return [f"// 按顺序借用 {access} 中每个 {field.type},任一访问失败立即向上传播。", f"for value in {access} {{ {method}(value)?; }}"] # 可选字段只有 Some/非空时才递归,缺省或 null 不访问。 if field.quantifier == "?": - return [f"if let Some(value) = {access} {{ {method}(value)?; }}"] - return [f"{method}({access})?;"] + return [f"// {access} 是可选字段,仅 Some 时访问其 {field.type} 子节点。", f"if let Some(value) = {access} {{ {method}(value)?; }}"] + return [f"// 访问 {access} 的 {field.type} 子结构或跨度,借用不转移节点所有权。", f"{method}({access})?;"] # 依稳定 schema 顺序处理每个定义,保留生成结果的可重复性。 for definition in definitions.values(): @@ -357,6 +488,7 @@ def rust_visitor(definitions): body += children(field, f"&node.{field.name}") # 带载荷和类型按判别分支访问,简单枚举没有子节点。 elif not definition.simple(): + body.append("// 按构造器只递归当前实际载荷,字符串等标量没有语法子节点。") body.append("match node {") # 每个构造器按稳定顺序生成,不合并名称相近但身份不同的分支。 for variant in definition.variants: @@ -375,6 +507,7 @@ def rust_visitor(definitions): members = ", ".join(f.name for f in visited) # 按构造器载荷形状生成 Rust match 模式,仅绑定需要访问的成员。 pattern = f"{definition.name}::{variant.name} {{ " + (members + ", " if members else "") + ".. }" + body.append("// " + variant_note(definition.name, variant)) body.append(pattern + " => {") # 为每个被选中的载荷展开对应序列、可选或单值访问。 for field in visited: @@ -385,6 +518,7 @@ def rust_visitor(definitions): visitor = "visitor" if any("visitor." in line for line in body) else "_visitor" # 完全无子结构的 walker 也不使用 node,生成下划线名字以满足警告检查。 node = "node" if body else "_node" + lines.append(f"/// 默认 visit_{snake(definition.name)} 或自定义访问器调用,按字段顺序借用 {definition.name} 的子结构;本函数不进入/退出当前节点。") lines.append(f"pub fn walk_{snake(definition.name)}({visitor}: &mut V, {node}: &{definition.name}) -> Result<(), V::Error> {{") lines.extend(body) lines += ["Ok(())", "}"] @@ -409,9 +543,19 @@ def editor_types(definitions, schema_id): def object_type(fields): return "{ " + " ".join(f"{f.name}{'?' if f.quantifier == '?' else ''}: {field_type(f)};" for f in fields) + " }" - lines = [HEADER, f'export const AST_SCHEMA_ID = "{schema_id}" as const;', "export interface Span { start: number; end: number; }"] + lines = [HEADER, "// 与 Rust wire 共同核对的 ASDL 原文协议身份;生成源码注释不改变此值。", f'export const AST_SCHEMA_ID = "{schema_id}" as const;', "// 原 UTF-8 半开字节范围;number 类型本身不验证安全整数、顺序或原文边界。", "export interface Span { start: number; end: number; }"] # 依稳定 schema 顺序处理每个定义,保留生成结果的可重复性。 for definition in definitions.values(): + # 节点及字段说明均输出成独立注释行,不改变 TypeScript 的类型/执行语句。 + lines.append("// " + type_note(definition.name)) + if definition.fields is not None: + for field in definition.fields: + lines.append(f"// {field.name}: {field_note(definition.name, field)}") + else: + for variant in definition.variants: + lines.append(f"// {variant.name}: {variant_note(definition.name, variant)}") + for field in variant.fields: + lines.append(f"// {variant.name}.{field.name}: {field_note(definition.name, field)}") # 产品按字段顺序生成;和类型则按构造器分支选择对应形状。 if definition.fields is not None: lines.append(f"export type {definition.name} = {object_type(definition.fields)};") @@ -436,8 +580,9 @@ def editor_types(definitions, schema_id): value = '{ kind: ' + json.dumps(variant.name) + ';' + data + ' }' variants.append(value) lines.append(f"export type {definition.name} = " + " | ".join(variants) + ";") - lines += ["export interface Visitor {", "visitSpan?(node: Span): boolean | void;"] - lines.extend(f"visit{name}?(node: {name}): boolean | void;" for name in definitions) + lines += ["// 编辑器可选节点回调;walk 先回调,返回 false 只剪掉当前子树,其他值继续递归。", "export interface Visitor {", "// 跨度叶子回调没有子结构,其返回值不参与后续剪枝。", "visitSpan?(node: Span): boolean | void;"] + for name in definitions: + lines += [f"// walk{name} 到达 {name} 时调用,false 跳过该节点的后续子字段。", f"visit{name}?(node: {name}): boolean | void;"] lines.append("}") # 访问器生成共用的字段递归模板:跳过普通文本/标识,仅递归节点或显式跨度。 @@ -449,16 +594,18 @@ def editor_types(definitions, schema_id): function = "walkSpan" if field.type == "span" else f"walk{field.type}" # 序列优先映射到 Vec/Array/array;容器本身处理重复元素并打断 Rust 按值递归。 if field.quantifier == "*": - return [f"for (const value of {access}) {function}(value, visitor);"] + return [f"// 按声明顺序访问 {access} 列表中的每个 {field.type} 子节点。", f"for (const value of {access}) {function}(value, visitor);"] # 可选字段只有 Some/非空时才递归,缺省或 null 不访问。 if field.quantifier == "?": - return [f"if ({access} != null) {function}({access}, visitor);"] - return [f"{function}({access}, visitor);"] + return [f"// {access} 可缺省或为 null,只有存在时才访问该子节点。", f"if ({access} != null) {function}({access}, visitor);"] + return [f"// 递归访问 {access} 的 {field.type} 子结构或跨度。", f"{function}({access}, visitor);"] + lines.append("// 上层 walker/宿主调用的跨度叶子入口,仅通知 visitSpan,不验证输入或修改节点。") lines.append("export function walkSpan(node: Span, visitor: Visitor): void { visitor.visitSpan?.(node); }") # 依稳定 schema 顺序处理每个定义,保留生成结果的可重复性。 for definition in definitions.values(): - lines += [f"export function walk{definition.name}(node: {definition.name}, visitor: Visitor): void {{", f"if (visitor.visit{definition.name}?.(node) === false) return;"] + lines.append(f"// 上层 walker 或宿主调用,先通知 {definition.name} 回调,再按 ASDL 顺序访问子结构;不修改树。") + lines += [f"export function walk{definition.name}(node: {definition.name}, visitor: Visitor): void {{", "// 仅显式 false 剪枝,未提供回调或返回 void 则继续访问。", f"if (visitor.visit{definition.name}?.(node) === false) return;"] # 产品按字段顺序生成;和类型则按构造器分支选择对应形状。 if definition.fields is not None: # 产品字段依声明顺序递归,保证访问顺序与数据契约一致。 @@ -466,9 +613,11 @@ def editor_types(definitions, schema_id): lines += children(field, "node." + field.name) # 带载荷和类型按判别分支访问,简单枚举没有子节点。 elif not definition.simple(): + lines.append("// 判别 kind 只选择当前载荷,普通文本/数字拼写没有进一步语法子结构。") lines.append("switch (node.kind) {") # 每个构造器按稳定顺序生成,不合并名称相近但身份不同的分支。 for variant in definition.variants: + lines.append("// " + variant_note(definition.name, variant)) lines.append(f"case {json.dumps(variant.name)}:") for field in variant.fields: # 单项载荷取 node.data,多字段载荷再按字段名取 node.data.。 -- 2.52.0 From 903b3bbf9da328d4e6bfefb4354af3b0e9b30aee Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 19:11:16 +0800 Subject: [PATCH 47/62] docs: fill annotation audit gaps in object and type paths --- crates/core/tests/types.rs | 1 + crates/frontend/tests/reader.rs | 1 + crates/objects/src/code.rs | 1 + crates/objects/src/effects.rs | 2 ++ crates/objects/src/heap.rs | 4 ++++ crates/objects/src/instruction.rs | 1 + crates/objects/src/native.rs | 3 +++ crates/objects/src/number.rs | 13 +++++++++++++ crates/objects/src/store.rs | 4 ++++ crates/objects/src/store_builtins.rs | 2 ++ crates/objects/src/types.rs | 4 +++- crates/objects/tests/store.rs | 1 + 12 files changed, 36 insertions(+), 1 deletion(-) diff --git a/crates/core/tests/types.rs b/crates/core/tests/types.rs index 4655662..347b7b7 100644 --- a/crates/core/tests/types.rs +++ b/crates/core/tests/types.rs @@ -340,6 +340,7 @@ fn failed_nominal_return_releases_value_and_captured_type_roots() { let expected = vm .new_type("Expected", roots.object, TypeConstructor::Record) .unwrap(); + // 创建与 Expected 同父但身份不同的类型,返回其实例应触发名义返回约束失败。 let other = vm .new_type("Other", roots.object, TypeConstructor::Record) .unwrap(); diff --git a/crates/frontend/tests/reader.rs b/crates/frontend/tests/reader.rs index 3850cf8..37e5451 100644 --- a/crates/frontend/tests/reader.rs +++ b/crates/frontend/tests/reader.rs @@ -160,6 +160,7 @@ impl SourceFile { impl Drop for SourceFile { /// 退出文件测试作用域时清理文件,忽略已不存在等删除错误,避免 Drop 再次失败。 fn drop(&mut self) { + // 只尽力删除测试文件,显式忽略删除结果,不在析构中制造第二个错误。 let _ = std::fs::remove_file(&self.0); } } diff --git a/crates/objects/src/code.rs b/crates/objects/src/code.rs index 6ffaf0f..7c44c72 100644 --- a/crates/objects/src/code.rs +++ b/crates/objects/src/code.rs @@ -459,6 +459,7 @@ fn verify( let height = heights[index].unwrap(); // 逻辑操作码与完整 u32 参数;前缀已由解码器合并。 let Instruction { opcode, argument } = instruction.instruction; + // 完整 u32 操作数转成当前平台索引/数量,用于下面栈需求与布局计算。 let argument = argument as usize; // required 是最深访问需求,popped/pushed 是净变化来源;Copy/Swap 不弹出其深层项。 let (required, popped, pushed) = match opcode { diff --git a/crates/objects/src/effects.rs b/crates/objects/src/effects.rs index 2dafb7a..45f6c46 100644 --- a/crates/objects/src/effects.rs +++ b/crates/objects/src/effects.rs @@ -28,6 +28,7 @@ enum Fact { impl Fact { // 把实际值降为抽象身份事实,直接标量不保留其内容。 fn value(value: Value) -> Self { + // 对象保留借用身份,标量统一变成 Unknown;不把抽象事实当作实际求值结果。 match value { Value::Object(object) => Self::Object(object), _ => Self::Unknown, @@ -35,6 +36,7 @@ impl Fact { } // 只从普通对象事实提取身份,不把方法对当作对象。 fn object(self) -> Option { + // 仅 Object 事实可用于属性接收者或调用身份,Unknown/Null/Method 都不冒充普通对象。 match self { Self::Object(object) => Some(object), _ => None, diff --git a/crates/objects/src/heap.rs b/crates/objects/src/heap.rs index c10d265..0ffa60f 100644 --- a/crates/objects/src/heap.rs +++ b/crates/objects/src/heap.rs @@ -118,6 +118,7 @@ pub enum ReadDependency { impl ReadDependency { /// 缓存/读取分析使用者取得观察所属对象,不创建外部拥有根。 pub fn object(&self) -> ObjectRef { + // 三种依赖形状都提取其观察对象身份,不重新读取内容或改变依赖精度。 match self { Self::Object { object, .. } | Self::DictionaryKind { object } @@ -311,6 +312,7 @@ impl Heap { ))?; // 锁的取得顺序与结束路径一致:先分类区间,再精确依赖区间。 let mut regions = scope.regions.lock().expect("read tracker poisoned"); + // 与分类区间相同锁顺序取得精确依赖区间栈,令牌检查必须同时核对两者。 let mut precise = scope .dependency_regions .lock() @@ -564,6 +566,7 @@ impl Heap { let object = self.get_mut(reference)?; // 暂存旧边/旧载荷,等新状态与通知建立后再释放旧状态。 let old_edges = std::mem::replace(&mut object.payload_edges, edges); + // 旧载荷也要暂存到变更通知及旧引用边释放之后再析构,保持状态提交顺序。 let old_payload = std::mem::replace(&mut object.payload, Box::new(payload)); self.record_change(reference, ChangeKey::Contents); self.release_edges(old_edges); @@ -966,6 +969,7 @@ mod tests { let mut heap = Heap::new().unwrap(); // owner 接收属性边,target 模拟无法再增加强计数的对象。 let owner = heap.allocate(Box::new(Record)).unwrap(); + // 另建目标对象,随后将强计数推至上限,测试 retain 失败不能修改外部根或 owner 的边。 let target = heap.allocate(Box::new(Record)).unwrap(); // 仅改强计数,外部根仍为一;后面的断言检查失败是否意外改动 external_refs。 heap.get_mut(target).unwrap().header.strong_refs = usize::MAX; diff --git a/crates/objects/src/instruction.rs b/crates/objects/src/instruction.rs index 217d2ca..b46219d 100644 --- a/crates/objects/src/instruction.rs +++ b/crates/objects/src/instruction.rs @@ -171,6 +171,7 @@ impl Instruction { pub const fn compare(operation: CompareOp) -> Self { Self::new( Opcode::CompareOp, + // 将六种比较映射为稳定字节码编号,必须与 eval 的 CompareOp 解码一致。 match operation { CompareOp::Lt => 0, CompareOp::Le => 1, diff --git a/crates/objects/src/native.rs b/crates/objects/src/native.rs index f85e0cb..2c34e17 100644 --- a/crates/objects/src/native.rs +++ b/crates/objects/src/native.rs @@ -90,6 +90,7 @@ pub enum CallType { Union(Vec), } impl From for CallType { + /// 宿主用单个 AnnotationType 构建原生签名时由 From/Into 包成 Single;这里不新增类型边或校验堆身份。 fn from(value: crate::AnnotationType) -> Self { Self::Single(value) } @@ -479,6 +480,7 @@ impl<'a> NativeContext<'a> { context.protect(&Value::Object(function))?; // 可选完整签名存在时才校验/保护;缺失元数据保留既有调用协议。 if let Some(signature) = &signature { + // 逐项保护签名持有的类型/能力定义,使回调执行期间不依赖宿主另外保留根。 for reference in signature.references() { context.protect(&Value::Object(reference))?; } @@ -603,6 +605,7 @@ impl ObjectPayload for ContextNativeFunction { fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { // 签名中的参数、可变参数和返回约束定义也属于载荷拥有边。 if let Some(signature) = &self.signature { + // 按签名实际引用报告每条拥有边,包括重复出现的定义,不因某个联合候选已匹配而省略。 for reference in signature.references() { visit(reference); } diff --git a/crates/objects/src/number.rs b/crates/objects/src/number.rs index 3d2bd80..5a76016 100644 --- a/crates/objects/src/number.rs +++ b/crates/objects/src/number.rs @@ -54,6 +54,7 @@ impl EndorInteger { pub fn to_i64(&self) -> Option { // 先读 u64 幅值以容纳 i64::MIN 的绝对值,再检查有符号范围。 let magnitude = self.digits.parse::().ok()?; + // 根据符号选择有符号范围检查,负数还需容纳 i64::MIN 的额外一单位幅值。 if self.negative { // 最小负数的幅值比最大正数多一,必须单独处理。 if magnitude == (i64::MAX as u64) + 1 { @@ -70,6 +71,7 @@ impl EndorInteger { impl fmt::Display for EndorInteger { /// Display/宿主诊断按当前表示输出,精确表示不经二进制浮点中转。 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // 仅负值输出前导减号,数字幅值随后原样写出,不通过浮点格式化。 if self.negative { f.write_str("-")?; } @@ -104,10 +106,13 @@ impl EndorDecimal { } // 分别记录已有小数点、数字数量与尾数是否全零。 let mut decimal_point = false; + // 统计尾数数字个数,允许前导零,但至少要有一个数字。 let mut digits = 0_usize; + // 初始假设尾数全零,每读到一个非零数字便保持为 false,用于零值语义。 let mut zero = true; // 按 ASCII 字节扫描,第二个点或其他字符都拒绝。 for byte in mantissa.bytes() { + // 数字更新计数及全零状态,首个点只登记位置,第二个点或其他字节明确失败。 match byte { b'.' if !decimal_point => decimal_point = true, b'0'..=b'9' => { @@ -209,6 +214,7 @@ pub enum EndorReal { impl EndorReal { /// 数值真值/分母检查使用的零判断,具体实现按表示处理。 pub fn is_zero(&self) -> bool { + // 按实际实数表示判断零;分数看分子,二进制浮点按其原有 f64 零语义判断。 match self { Self::Integer(value) => value.is_zero(), Self::Decimal(value) => value.is_zero(), @@ -221,6 +227,7 @@ impl EndorReal { impl fmt::Display for EndorReal { /// Display/宿主诊断按当前表示输出,精确表示不经二进制浮点中转。 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // 各精确表示委托自己的 Display;BinaryFloat 才按存储的浮点位模式显示。 match self { Self::Integer(value) => value.fmt(f), Self::Decimal(value) => value.fmt(f), @@ -275,6 +282,7 @@ impl EndorNumber { /// VM 算术/转换查询实数,Complex 即使虚部为零也返回 None。 pub fn as_real(&self) -> Option<&EndorReal> { + // 只有 Real 能借用实数部分;Complex 即使虚部为零也不在此自动降级。 match self { Self::Real(value) => Some(value), Self::Complex { .. } => None, @@ -283,6 +291,7 @@ impl EndorNumber { /// VM 快路径仅接受整数变体,不自动转小数或可整除分数。 pub fn as_i64(&self) -> Option { + // 仅整数实数尝试收窄为 i64,小数、分数、浮点和复数均不隐式转换。 match self { Self::Real(EndorReal::Integer(value)) => value.to_i64(), _ => None, @@ -291,6 +300,7 @@ impl EndorNumber { /// 数值真值/分母检查使用的零判断,具体实现按表示处理。 pub fn is_zero(&self) -> bool { + // 实数委托自身零判断;复数要求实部和虚部都为零。 match self { Self::Real(value) => value.is_zero(), Self::Complex { real, imaginary } => real.is_zero() && imaginary.is_zero(), @@ -315,6 +325,7 @@ impl From for EndorNumber { impl fmt::Display for EndorNumber { /// Display/宿主诊断按当前表示输出,精确表示不经二进制浮点中转。 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // 实数保持原表示,复数分别显示两个实数部分和显式虚数单位。 match self { Self::Real(value) => value.fmt(f), Self::Complex { real, imaginary } => write!(f, "({real} + ({imaginary})i)"), @@ -412,6 +423,7 @@ pub enum ArithmeticOp { impl ArithmeticOp { /// eval 解 BinaryArithmetic 的 0..3 运算编号,其他值返回 None。 pub fn from_argument(value: u32) -> Option { + // 只接受字节码定义的四个算术编号,其他值返回 None 交调用方拒绝。 match value { 0 => Some(Self::Add), 1 => Some(Self::Subtract), @@ -617,6 +629,7 @@ fn exact_real(value: &EndorReal) -> Result { s.parse() .map_err(|_| VmError::InvalidArgument("invalid exact number")) }; + // 不同实数表示转成运算用精确有理数;浮点必须有限,十进制另检查指数运算预算。 match value { EndorReal::Integer(n) => Ok(BigRational::from_integer(integer(&n.to_string())?)), EndorReal::Rational(n) => Ok(BigRational::new( diff --git a/crates/objects/src/store.rs b/crates/objects/src/store.rs index 5e078e4..2cec058 100644 --- a/crates/objects/src/store.rs +++ b/crates/objects/src/store.rs @@ -196,6 +196,7 @@ impl ObjectStore { } // 旧式宿主原地赋值在调用前检查写屏障;实现不得悄悄改变 trace 中的托管边。 self.heap.prepare_change(target)?; + // 调用宿主原地赋值协议,返回是否确实改变;只有 true 才发布 Contents 变更。 let changed = self .object(target)? .payload() @@ -295,7 +296,9 @@ impl ObjectStore { pub fn attribute_path(&self, reference: ObjectRef) -> Result, VmError> { // path 保存访问顺序,seen 检测转发环,current 是本轮需要验证并展开的对象。 let mut path = Vec::new(); + // 记录已经展开的对象身份,转发环在再次遇到同一对象时失败。 let mut seen = std::collections::BTreeSet::new(); + // 从原始门面开始逐跳追踪 attribute_target,最终返回整个存储路径。 let mut current = reference; // 每次至少加入一个对象;遇到无转发目标才正常结束,因此成功路径一定非空。 loop { @@ -364,6 +367,7 @@ impl ObjectStore { ) -> Result { // 借用双方载荷;这条底层协议路径不通过 object,因此不自动收集对象版本依赖。 let left_payload = self.heap.get(left)?.payload(); + // 借用右侧载荷,左侧不支持时才反向比较;此底层访问不自动登记内容依赖。 let right_payload = self.heap.get(right)?.payload(); // 左侧能处理就立即返回;Some(false) 也是已实现结果,不能继续尝试右侧。 if let Some(result) = left_payload.compare(right_payload, operation)? { diff --git a/crates/objects/src/store_builtins.rs b/crates/objects/src/store_builtins.rs index 13702ce..024f182 100644 --- a/crates/objects/src/store_builtins.rs +++ b/crates/objects/src/store_builtins.rs @@ -188,6 +188,7 @@ impl ObjectStore { self.key_hash(&key)?; // 对象值必须来自本堆且仍有效;标量无需额外句柄检查。 self.validate_value(&value)?; + // 先找相等键再决定覆盖或追加,保证保留首个键对象及插入顺序。 if let Some(index) = self.dict_position(&dict, &key)? { // 相等键只覆盖值,保留第一次插入的键对象及其顺序,不保存本次传入的替代键。 dict.entries[index].1 = value; @@ -241,6 +242,7 @@ impl ObjectStore { self.validate_value(value)?; // 直接值使用标准哈希器;对象委托各自实现,可由载荷拒绝不可哈希种类。 let mut hasher = DefaultHasher::new(); + // 标量按已定义类别散列;对象委托载荷哈希,尚无数值哈希协议时拒绝 Number。 match value { Value::Nil => "nil".hash(&mut hasher), Value::Bool(value) => value.hash(&mut hasher), diff --git a/crates/objects/src/types.rs b/crates/objects/src/types.rs index cbd58e0..cca867a 100644 --- a/crates/objects/src/types.rs +++ b/crates/objects/src/types.rs @@ -522,6 +522,7 @@ impl ObjectStore { let mut current = Some(type_object); // 基类固定于创建,逐层校验类型句柄而不是按名称检索。 while let Some(type_object) = current { + // 本层必须是有效 Type 载荷,随后只在该层方法表查找名称,未命中才沿固定基类继续。 let definition = self.payload::(type_object, "Type")?; // 找到方法即返回借用身份,不替调用方 retain。 if let Some(function) = definition.methods.get(name) { @@ -1075,10 +1076,11 @@ impl ObjectStore { /// 方法/能力更新前扫描存活类型,返回包含 base 自身的全部子类型借用身份。 fn descendant_types(&self, base: ObjectRef) -> Result, VmError> { self.payload::(base, "Type")?; - // 去重能力身份,不建立堆外拥有注册表或延长能力寿命。 + // 收集受更新影响的类型借用身份;不创建后代拥有索引,也不延长类型寿命。 let mut found = vec![]; // 扫描当前存活对象;只选类型载荷及其名义子类型。 for reference in self.heap.live_references() { + // 只收集仍存活的 Type 对象及 base 的名义后代,普通实例和无关类型不参与更新检查。 if self .object(reference)? .payload() diff --git a/crates/objects/tests/store.rs b/crates/objects/tests/store.rs index 7b57fda..83129be 100644 --- a/crates/objects/tests/store.rs +++ b/crates/objects/tests/store.rs @@ -133,6 +133,7 @@ fn read_only_scopes_block_existing_container_and_type_writes_but_allow_fresh_res let record = store.new_object().unwrap(); // 已有字典受同一只读屏障保护,不只保护普通属性。 let dict = store.new_dict().unwrap(); + // 进入只读前创建的 Cell 同样属于受保护堆对象,不能靠内部值更新绕过写屏障。 let cell = store .new_cell(Some(objects::Value::Number(1_i64.into()))) .unwrap(); -- 2.52.0 From 57cd32eb5cd5bb7dffd654bdf94d6f3ffa8bebdb Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 19:14:55 +0800 Subject: [PATCH 48/62] docs: explain match assertions and annotation type distinctions --- crates/core/tests/builtins.rs | 3 +++ crates/core/tests/execution.rs | 13 +++++++++---- crates/core/tests/method_contracts.rs | 6 ++++++ crates/core/tests/native_context.rs | 8 +++++++- crates/core/tests/runtime.rs | 4 ++++ crates/core/tests/typed_traits.rs | 1 + crates/core/tests/types.rs | 2 ++ crates/frontend/tests/ast.rs | 2 ++ crates/frontend/tests/reader.rs | 2 ++ crates/frontend/tests/syntax.rs | 1 + crates/objects/src/heap.rs | 3 +++ crates/objects/src/native.rs | 1 + crates/objects/src/number.rs | 2 ++ crates/objects/src/types.rs | 8 ++++++++ crates/objects/tests/attribute_storage.rs | 7 ++++++- crates/objects/tests/store.rs | 1 + 16 files changed, 58 insertions(+), 6 deletions(-) diff --git a/crates/core/tests/builtins.rs b/crates/core/tests/builtins.rs index de46a70..a942d6e 100644 --- a/crates/core/tests/builtins.rs +++ b/crates/core/tests/builtins.rs @@ -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)); } diff --git a/crates/core/tests/execution.rs b/crates/core/tests/execution.rs index 4a790de..4c3567b 100644 --- a/crates/core/tests/execution.rs +++ b/crates/core/tests/execution.rs @@ -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")) )); diff --git a/crates/core/tests/method_contracts.rs b/crates/core/tests/method_contracts.rs index 40bda5d..d355421 100644 --- a/crates/core/tests/method_contracts.rs +++ b/crates/core/tests/method_contracts.rs @@ -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, diff --git a/crates/core/tests/native_context.rs b/crates/core/tests/native_context.rs index 09d77f6..8f74b97 100644 --- a/crates/core/tests/native_context.rs +++ b/crates/core/tests/native_context.rs @@ -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); diff --git a/crates/core/tests/runtime.rs b/crates/core/tests/runtime.rs index 1d81305..4ba7ebc 100644 --- a/crates/core/tests/runtime.rs +++ b/crates/core/tests/runtime.rs @@ -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))); } diff --git a/crates/core/tests/typed_traits.rs b/crates/core/tests/typed_traits.rs index 23cb45d..2245181 100644 --- a/crates/core/tests/typed_traits.rs +++ b/crates/core/tests/typed_traits.rs @@ -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!( diff --git a/crates/core/tests/types.rs b/crates/core/tests/types.rs index 347b7b7..03ce664 100644 --- a/crates/core/tests/types.rs +++ b/crates/core/tests/types.rs @@ -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 { .. }) diff --git a/crates/frontend/tests/ast.rs b/crates/frontend/tests/ast.rs index 7d2ce89..ef4a9f4 100644 --- a/crates/frontend/tests/ast.rs +++ b/crates/frontend/tests/ast.rs @@ -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(_)) diff --git a/crates/frontend/tests/reader.rs b/crates/frontend/tests/reader.rs index 37e5451..851a525 100644 --- a/crates/frontend/tests/reader.rs +++ b/crates/frontend/tests/reader.rs @@ -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 diff --git a/crates/frontend/tests/syntax.rs b/crates/frontend/tests/syntax.rs index b57a185..3d5866d 100644 --- a/crates/frontend/tests/syntax.rs +++ b/crates/frontend/tests/syntax.rs @@ -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 { diff --git a/crates/objects/src/heap.rs b/crates/objects/src/heap.rs index 0ffa60f..8bdc255 100644 --- a/crates/objects/src/heap.rs +++ b/crates/objects/src/heap.rs @@ -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; diff --git a/crates/objects/src/native.rs b/crates/objects/src/native.rs index 2c34e17..8bd8fc1 100644 --- a/crates/objects/src/native.rs +++ b/crates/objects/src/native.rs @@ -119,6 +119,7 @@ impl CallType { pub fn is_any(&self) -> bool { self.alternatives() .iter() + // 只检测是否存在 Any 候选,不据此省略联合中其他定义引用的验证或保活。 .any(|value| matches!(value, crate::AnnotationType::Any)) } /// 调用/签名检查确认联合非空,且每个托管类型/能力引用有效并有正确载荷。 diff --git a/crates/objects/src/number.rs b/crates/objects/src/number.rs index 5a76016..0def382 100644 --- a/crates/objects/src/number.rs +++ b/crates/objects/src/number.rs @@ -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)), }) } diff --git a/crates/objects/src/types.rs b/crates/objects/src/types.rs index cca867a..487d6e8 100644 --- a/crates/objects/src/types.rs +++ b/crates/objects/src/types.rs @@ -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::()) } AnnotationType::String => { + // String 同时接受直接 Text;对象表示只接受内置字符串载荷。 matches!(value, Value::Text(_)) + // 对象必须确为 EndorStringObject,不通过 str 协议把任意对象隐式转文本。 || matches!(value,Value::Object(object) if self.object(*object)?.payload().as_any().is::()) } + // 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::()) } AnnotationType::Trait => { + // Trait 要求实际能力定义载荷,与实现某能力的普通实例不同。 matches!(value,Value::Object(object) if self.object(*object)?.payload().as_any().is::()) } AnnotationType::Nominal(definition) => match value { diff --git a/crates/objects/tests/attribute_storage.rs b/crates/objects/tests/attribute_storage.rs index f8047b5..0267331 100644 --- a/crates/objects/tests/attribute_storage.rs +++ b/crates/objects/tests/attribute_storage.rs @@ -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 { .. }) diff --git a/crates/objects/tests/store.rs b/crates/objects/tests/store.rs index 83129be..0a14d1b 100644 --- a/crates/objects/tests/store.rs +++ b/crates/objects/tests/store.rs @@ -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(); } -- 2.52.0 From ecba02c20ef52954ca75b8c1044d8b3a4611e9d2 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 19:19:39 +0800 Subject: [PATCH 49/62] docs(core): correct public execution and ownership contracts --- crates/core/src/eval.rs | 13 +++++++------ crates/core/src/frame.rs | 7 ++++--- crates/core/src/vm.rs | 4 +++- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/crates/core/src/eval.rs b/crates/core/src/eval.rs index 24f43b2..54489f9 100644 --- a/crates/core/src/eval.rs +++ b/crates/core/src/eval.rs @@ -94,7 +94,7 @@ impl Vm { ) -> Result<(Value, crate::ReadSet), VmError> { // 快照进入前对象身份;之后新建结果允许初始化,但已有对象受写屏障保护。 let scope = self.objects.begin_read_only(); - // 先保存调用结果,使成功和错误都能在返回前释放临时函数根。 + // 先保存调用结果,成功和错误都须关闭本层只读作用域;不消费调用者的函数或参数根。 let result = self.call_with_limits(function, arguments, limits); // 结束域先取回借用观察,之后才将调用成功值与观察组合返回。 let reads = self.objects.end_read_only_precise(scope)?; @@ -116,7 +116,7 @@ impl Vm { } // 新调用从空诊断开始,后续失败才写入该次剩余帧回溯。 self.last_traceback.clear(); - // 累计调用准备和指令工作量,切片恢复不会将其清零。 + // 本次同步调用独立累计调用准备和指令工作量,整个源码调用栈共用这一硬预算。 let mut executed = 0; // 原生调用可能在准备阶段完成并直接移交拥有结果,源码则进入帧循环。 let frame = match self.prepare_call(function, arguments.to_vec(), &mut executed, limits)? { @@ -417,7 +417,7 @@ impl Vm { if slice == 0 { return Err(VmError::InvalidArgument("execution slice must be positive")); } - // 把准备状态包装为托管载荷,trace 会为其保留与帧根一致的内部强边。 + // 借用并校验现有协程载荷,读取其硬预算和停放状态;此处尚未替换载荷。 let coroutine = self.payload::(reference, "coroutine")?; // 硬限制来自创建时状态,不能在每个量子重新给予预算。 let limits = coroutine.limits; @@ -605,7 +605,7 @@ impl Vm { pub fn cancel_coroutine(&mut self, reference: ObjectRef) -> Result { // 先拒绝只读上下文,避免用托管协程绕过活跃只读保护。 self.check_coroutine_entry()?; - // 把准备状态包装为托管载荷,trace 会为其保留与帧根一致的内部强边。 + // 借用并校验现有协程状态,先排除正在执行或已结束的情况,再提交取消。 let coroutine = self.payload::(reference, "coroutine")?; // 正执行状态拒绝取消,避免在 Rust 回调重入时移除执行者状态。 if matches!(coroutine.state, CoroutineState::Running) { @@ -615,7 +615,7 @@ impl Vm { if matches!(coroutine.state, CoroutineState::Finished) { return Ok(false); } - // 硬限制来自创建时状态,不能在每个量子重新给予预算。 + // 取消后仍保留创建时硬限制元数据,只清理帧/结果拥有边,不创建新执行预算。 let limits = coroutine.limits; // 取消保留累计诊断计数,不把已经执行的副作用或预算消耗抹除。 let executed = coroutine.executed; @@ -632,7 +632,8 @@ impl Vm { /// Outer-to-inner execution locations from the last failed call/execute. /// These are copied metadata, not heap references or language exceptions. - /// 宿主错误报告借用最近失败的复制位置元数据,不保留故障对象图。 + /// 宿主错误报告借用最近记录的复制位置元数据,不保留故障对象图。 + /// 仅源码帧执行失败路径生成回溯,参数准备或入口拒绝等错误不保证产生新的非空条目。 pub fn last_traceback(&self) -> &[ExecutionLocation] { &self.last_traceback } diff --git a/crates/core/src/frame.rs b/crates/core/src/frame.rs index 8f40c23..7f4bfab 100644 --- a/crates/core/src/frame.rs +++ b/crates/core/src/frame.rs @@ -1,5 +1,5 @@ //! eval 创建、推进并清理执行状态的数据结构;这里不执行指令,也不自动释放复制句柄。 -//! 活跃帧用外部执行根保留对象;停放到 Coroutine 载荷后由其 trace 边保留同一组身份。 +//! 活跃帧及 VM 内的只读续执行帧用外部执行根保留对象;停放到 Coroutine 载荷后改由 trace 边保留。 //! Arc 只共享 Rust 帧/代码内存,根与内部边的转换由 eval 显式完成。 use std::sync::Arc; @@ -34,6 +34,7 @@ impl NativeReturn { /// Internal activation record, analogous to _PyInterpreterFrame. It has no /// EndorObject header; introspection objects can be added independently later. #[derive(Debug)] +/// eval 内部持有的源码帧;公开 getter 只能观察已有帧引用,当前 Vm 不提供活动帧枚举或借出入口。 pub struct EndorFrame { /// 活跃帧拥有的函数根;函数强边使 code/globals/builtins 保持可达。 pub(crate) function: ObjectRef, @@ -134,7 +135,7 @@ impl Default for ExecutionLimits { /// owned value plus borrowed read dependencies. Instruction counts accumulate. #[derive(Debug)] pub enum ExecutionProgress { - /// 软切片耗尽,状态仍保存于 VM/托管协程中,不能重放此前代码。 + /// 只读软切片耗尽,帧和只读作用域仍保存于 VM 的单个续执行槽,不重放此前代码。 Pending { /// 从执行开始累计的计数,不是当前切片的局部数量。 instructions: usize, @@ -220,7 +221,7 @@ impl EndorFrame { } // 每个委托约束都独立持有定义根,重复引用不能去重。 for check in &self.native_returns { - // 参数联合等元数据可能包含多个定义,枚举完整引用集合。 + // 返回联合约束可能包含多个定义,按实际出现次数枚举完整引用集合。 for reference in check.expected.references() { visit(reference); } diff --git a/crates/core/src/vm.rs b/crates/core/src/vm.rs index 56bf63a..ae71f82 100644 --- a/crates/core/src/vm.rs +++ b/crates/core/src/vm.rs @@ -44,11 +44,13 @@ impl Vm { } /// 宿主参数检查委托 Store,统一源码/原生数量区间并扣除已绑定接收者。 + /// None 表示未发布数量元数据,不能仅凭此结果断定该对象可以调用。 pub fn call_arity(&self, function: ObjectRef) -> Result, VmError> { self.objects.call_arity(function) } /// 宿主初始化 Function 名义类型配置,Store 保存弱引用用于后续函数分配。 + /// 同一身份可重复设置,配置后不能换成另一类型;存活由宿主根或已附着对象等其他拥有图保证。 pub fn set_function_type(&mut self, definition: ObjectRef) -> Result<(), VmError> { self.objects.set_function_type(definition) } @@ -198,7 +200,7 @@ impl Vm { /// 嵌入方创建空 Store 和空执行状态;不注册类型、函数或语言全局空间。 pub fn new() -> Result { - // 空状态与 Store::new 保持一致,执行时按需建立全局空间与帧。 + // 空状态与 Store::new 保持一致;对象/环境由调用方显式分配,执行入口按需准备帧。 Ok(Self { objects: ObjectStore::new()?, last_traceback: Vec::new(), -- 2.52.0 From d451caf0f1e121b0e7580e3e6a373421b7a57ba5 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 19:25:51 +0800 Subject: [PATCH 50/62] docs(objects): clarify protocol tracking and ownership boundaries --- crates/core/src/eval.rs | 1 + crates/objects/src/builtins.rs | 18 +++++++++--------- crates/objects/src/changes.rs | 3 ++- crates/objects/src/object.rs | 5 ++++- crates/objects/src/store.rs | 7 +++++++ crates/objects/src/store_builtins.rs | 3 +++ 6 files changed, 26 insertions(+), 11 deletions(-) diff --git a/crates/core/src/eval.rs b/crates/core/src/eval.rs index 54489f9..0730e78 100644 --- a/crates/core/src/eval.rs +++ b/crates/core/src/eval.rs @@ -1126,6 +1126,7 @@ impl Vm { Value::Bool(value) => Ok(*value), Value::Text(value) => Ok(!value.is_empty()), Value::Number(value) => Ok(!value.is_zero()), + // 对象真值走 Store 的载荷协议;该底层入口当前不自动登记对象版本依赖。 Value::Object(reference) => self.is_true(*reference), } } diff --git a/crates/objects/src/builtins.rs b/crates/objects/src/builtins.rs index 2122185..9aa07e8 100644 --- a/crates/objects/src/builtins.rs +++ b/crates/objects/src/builtins.rs @@ -24,7 +24,7 @@ impl EndorStringObject { Self { text: text.into() } } - /// 字典 key_equal 和宿主查询借用 UTF-8 内容;借用不创建托管根,也不修改字符串。 + /// 字典 key_equal、eval 文本比较和宿主查询借用 UTF-8 内容;借用不创建托管根,也不修改字符串。 pub fn as_str(&self) -> &str { &self.text } @@ -41,7 +41,7 @@ impl EndorStringObject { } impl ObjectPayload for EndorStringObject { - /// ObjectStore::assign 调用,声明默认 assign_from 不会就地修改载荷;只读阶段可跳过写尝试,允许调用方重绑局部引用。 + /// ObjectStore::assign_from 调用,声明默认 assign_from 不会就地修改载荷;只读阶段可跳过写尝试,允许调用方重绑局部引用。 fn reference_assignment_only(&self) -> bool { true } @@ -120,7 +120,7 @@ impl EndorDictObject { } impl ObjectPayload for EndorDictObject { - /// ObjectStore::assign 调用,声明默认 assign_from 不会就地修改载荷;只读阶段可跳过写尝试,允许调用方重绑局部引用。 + /// ObjectStore::assign_from 调用,声明默认 assign_from 不会就地修改载荷;只读阶段可跳过写尝试,允许调用方重绑局部引用。 fn reference_assignment_only(&self) -> bool { true } @@ -174,7 +174,7 @@ impl EndorCellObject { } impl ObjectPayload for EndorCellObject { - /// ObjectStore::assign 调用,声明默认 assign_from 不会就地修改载荷;只读阶段可跳过写尝试,允许调用方重绑局部引用。 + /// ObjectStore::assign_from 调用,声明默认 assign_from 不会就地修改载荷;只读阶段可跳过写尝试,允许调用方重绑局部引用。 fn reference_assignment_only(&self) -> bool { true } @@ -276,7 +276,7 @@ impl ObjectPayload for EndorFunctionObject { fn is_function(&self) -> bool { true } - /// ObjectStore::assign 调用,声明默认 assign_from 不会就地修改载荷;只读阶段可跳过写尝试,允许调用方重绑局部引用。 + /// ObjectStore::assign_from 调用,声明默认 assign_from 不会就地修改载荷;只读阶段可跳过写尝试,允许调用方重绑局部引用。 fn reference_assignment_only(&self) -> bool { true } @@ -362,7 +362,7 @@ impl EndorBaseExceptionObject { self.suppress_context = true; self } - /// 分配前的构建器记录上下文强边;不自动改变 suppress_context,也不执行上下文异常。 + /// 分配前的构建器保存上下文句柄;allocate 后才登记强边,不自动改变 suppress_context 或执行异常。 pub fn with_context(mut self, context: ObjectRef) -> Self { self.context = Some(context); self @@ -390,11 +390,11 @@ impl EndorBaseExceptionObject { pub fn args(&self) -> &[Value] { &self.args } - /// ObjectStore::validate_builtin/trace 读取可选显式原因;返回借用身份,不创建新拥有根。 + /// ObjectStore::validate_builtin 和宿主读取可选显式原因;返回借用身份,不创建新拥有根。 pub fn cause(&self) -> Option { self.cause } - /// ObjectStore::validate_builtin/trace 读取可选上下文;即使已抑制显示,此强边仍参与追踪。 + /// ObjectStore::validate_builtin 和宿主读取可选上下文;即使已抑制显示,分配后的上下文边仍参与追踪。 pub fn context(&self) -> Option { self.context } @@ -413,7 +413,7 @@ impl EndorBaseExceptionObject { } impl ObjectPayload for EndorBaseExceptionObject { - /// ObjectStore::assign 调用,声明默认 assign_from 不会就地修改载荷;只读阶段可跳过写尝试,允许调用方重绑局部引用。 + /// ObjectStore::assign_from 调用,声明默认 assign_from 不会就地修改载荷;只读阶段可跳过写尝试,允许调用方重绑局部引用。 fn reference_assignment_only(&self) -> bool { true } diff --git a/crates/objects/src/changes.rs b/crates/objects/src/changes.rs index e07aa5a..6ca379a 100644 --- a/crates/objects/src/changes.rs +++ b/crates/objects/src/changes.rs @@ -23,7 +23,8 @@ pub struct ObjectChange { pub version: u64, } -/// Store::take_changes 返回上次取走后累计的合并结果;取走时重置内部批次。 +/// Store::take_changes 返回上次取走后累计发布的合并结果;取走时重置内部批次。 +/// 只读域中新建结果的允许写入更新对象版本,但不发布到此应用通知批次。 /// 溢出会丢弃精确条目并要求全面重验,消费者不能把空 changes 解释为没有修改。 #[derive(Debug, Default)] pub struct ChangeBatch { diff --git a/crates/objects/src/object.rs b/crates/objects/src/object.rs index f73bab2..48d3fb4 100644 --- a/crates/objects/src/object.rs +++ b/crates/objects/src/object.rs @@ -106,6 +106,7 @@ pub trait ObjectPayload: Any + fmt::Debug + Send + Sync { fn kind_name(&self) -> &'static str; /// Heap 在分配/替换载荷时收集内部强边(含重复边);GC 此后使用边快照。 + /// 单独调用 trace 只枚举借用身份,不替调用者取得拥有根。 /// 默认无边;实现不能在快照建立后通过内部可变性悄悄改变这些引用。 fn trace(&self, _visit: &mut dyn FnMut(ObjectRef)) {} @@ -249,6 +250,7 @@ impl EndorNativeFunctionObject { &self.name } /// 构造阶段补充完整签名,并同步数量限制;分配后签名中的引用由 Heap 边快照持有。 + /// 此构建器不验证签名结构;Store::native_signature 查询或 VM 调用时再校验完整契约。 pub fn with_signature(mut self, signature: crate::CallSignature) -> Self { self.arity = Some(signature.arity); self.signature = Some(signature); @@ -306,7 +308,8 @@ impl ObjectPayload for EndorNativeFunctionObject { fn call_arity(&self) -> Option { self.arity } - // VM 分派或宿主直接调用:先检查数量,再将同一参数切片借给 Rust 回调。 + // VM 分派或宿主直接调用:此载荷方法只检查数量,再将同一参数切片借给 Rust 回调。 + // 直接调用不执行 VM 的完整参数/返回类型检查,结果中的对象也仍借用;完整调用请经 Vm::call。 fn call(&self, arguments: &[Value]) -> Result { // 未声明 arity 时不在这里限制数量;声明后错误必须先于回调的任何副作用返回。 if let Some(arity) = self.arity { diff --git a/crates/objects/src/store.rs b/crates/objects/src/store.rs index 2cec058..6c6bb70 100644 --- a/crates/objects/src/store.rs +++ b/crates/objects/src/store.rs @@ -58,6 +58,7 @@ impl ObjectStore { } /// 缓存使用者重验一次观察;失效句柄或当前读取失败都表示依赖不再有效。 /// 经正常读取入口比较,因此若当前另有读取作用域,也会登记相应观察。 + /// 名称/字典种类比较当前状态而非永久失效位,删除遮蔽或恢复原值后可再次有效。 pub fn read_dependency_valid(&self, dependency: &crate::ReadDependency) -> bool { // 整对象检查版本;种类只检查仍是字典;名称检查当前 Option 是否保持相等。 match dependency { @@ -104,12 +105,14 @@ impl ObjectStore { } /// 宿主缓存或类型配置把有效强句柄转为弱句柄;不增加计数,也不消耗调用方已有根。 + /// 仍经 object 验证,处于只读追踪作用域时会登记此对象的版本读取。 pub fn downgrade(&self, reference: ObjectRef) -> Result { self.object(reference)?; Ok(WeakObjectRef(reference)) } /// 宿主升级弱句柄:存活则 retain 一份外部根,已释放返回 None;跨堆引用仍是错误。 + /// 存活验证经过 object,可能登记读取;升级失败不交付拥有根。 pub fn upgrade(&mut self, weak: WeakObjectRef) -> Result, VmError> { // 先验证代际与堆身份;只有 InvalidHandle 被解释为过期,其他错误原样传播。 match self.object(weak.0) { @@ -344,21 +347,25 @@ impl ObjectStore { } /// Vm::str 委托用户可见文本表示,默认载荷实现回退到 repr。 + /// 当前文本协议直接借用 Heap 载荷,不自动登记对象版本读取;不能把调用成功当成已收集内容依赖。 pub fn str(&self, reference: ObjectRef) -> Result { self.heap.get(reference)?.payload().str(reference) } /// VM 真值转换调用载荷协议;错误保持为错误,不把协议失败解释为 false。 + /// 当前真值协议直接借用 Heap 载荷,不自动登记对象版本读取;不能把调用成功当成已收集内容依赖。 pub fn is_true(&self, reference: ObjectRef) -> Result { self.heap.get(reference)?.payload().is_true() } /// 通用字典键准入及宿主哈希调用载荷协议,载荷可明确拒绝哈希。 + /// 当前哈希协议直接借用 Heap 载荷,不自动登记对象版本读取;不能把调用成功当成已收集内容依赖。 pub fn hash(&self, reference: ObjectRef) -> Result { self.heap.get(reference)?.payload().hash(reference) } /// VM 对象比较和字典对象键查找共用的双向协议;不运行源码函数比较方法。 + /// 此入口自身不登记双方版本;调用方其他读取可能另外收集依赖。 pub fn compare( &self, left: ObjectRef, diff --git a/crates/objects/src/store_builtins.rs b/crates/objects/src/store_builtins.rs index 024f182..5305e6d 100644 --- a/crates/objects/src/store_builtins.rs +++ b/crates/objects/src/store_builtins.rs @@ -80,6 +80,7 @@ impl ObjectStore { /// Vm 的执行入口及 eval 创建闭包时调用,组合代码、全局空间、默认值和捕获槽。 /// name 用作函数标识;code 指向可执行代码;closure 按该代码的自由变量顺序排列。 + /// 输入对象均借用,成功返回一份函数拥有根;代码、环境、默认值和捕获对象由函数内部边保活。 pub fn new_function( &mut self, name: impl Into, @@ -150,6 +151,7 @@ impl ObjectStore { /// eval 查找全局名和内置名时调用:既记录命中值,也记录“此名称不存在”。 /// 这里只查一个字典;全局未命中后再查 builtins 的顺序由 eval 决定。 /// 通用 dict_get 和条目迭代仍采用整字典依赖。 + /// 返回 Value 中对象身份仅借用;名称观察快照也不取得对象根。 pub fn dict_get_name( &self, dictionary: ObjectRef, @@ -201,6 +203,7 @@ impl ObjectStore { } /// Vm::dict_delete 删除一个键;返回值说明是否存在该键,错误通过 Result 单独传播。 + /// 缺键时只完成读取并返回 false,不触发替换写屏障;已有键在只读域内删除仍会被拒绝。 pub fn dict_delete(&mut self, dictionary: ObjectRef, key: &Value) -> Result { // 先复制载荷准备新状态;Rust clone 不 retain,提交前仍由旧字典持有原有引用边。 let mut dict = self.payload::(dictionary, "dict")?.clone(); -- 2.52.0 From 4880547e662b03f40b94e06bf1cba971c987f495 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 19:30:13 +0800 Subject: [PATCH 51/62] docs(objects): clarify native scope and contract validation phases --- crates/objects/src/contracts.rs | 8 +++++--- crates/objects/src/native.rs | 20 ++++++++++++++++---- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/crates/objects/src/contracts.rs b/crates/objects/src/contracts.rs index 1f91292..dae5279 100644 --- a/crates/objects/src/contracts.rs +++ b/crates/objects/src/contracts.rs @@ -85,6 +85,7 @@ impl CallType { /// Nominal inheritance and explicit capabilities participate; no structural /// or callable-signature inference is implied by this relation. /// 替换校验的值集合包含关系:provided 的每项都必须被 self 的至少一项接纳。 + /// check_substitution 用它检查参数逆变和返回协变;只检查声明,不调用实现或保留定义根。 pub fn accepts_type(&self, store: &ObjectStore, provided: &Self) -> Result { // 先验证联合全部定义边,再做包含/匹配判断,不能让无效分支藏在后面。 self.validate(store)?; @@ -429,7 +430,7 @@ impl ObjectStore { } Ok(()) } - /// set_method 提交前,以 Patch 校验受影响后代,避免先改表再发现错误。 + /// define_method 提交前,以 Patch 校验受影响后代,避免先改表再发现错误。 pub(super) fn validate_override_update( &self, owner: ObjectRef, @@ -455,7 +456,7 @@ impl ObjectStore { Ok(()) } /// Explicit override validation, including any typed capabilities. - /// VM/宿主显式启用普通覆盖检查,同时校验接收者的类型化能力约束。 + /// 宿主显式要求本次普通覆盖检查,同时校验接收者的类型化能力约束;不修改 VM/Store 检查开关。 pub fn check_method_override_call( &self, function: ObjectRef, @@ -466,7 +467,7 @@ impl ObjectStore { } /// Typed capabilities are explicit opt-ins and always checked. Ordinary /// override checking remains configurable by the VM's source-check flag. - /// eval 原生调用前核对实际方法;能力约束始终检查,普通覆盖由 overrides 控制。 + /// eval 的源码建帧、原生调用及 NativeContext 准备时核对方法;能力约束始终检查,普通覆盖由 overrides 控制。 pub fn check_method_contract_call( &self, function: ObjectRef, @@ -526,6 +527,7 @@ impl ObjectStore { /// Borrowed expected results for all typed capabilities of this effective /// method. The evaluator retains definitions before entering the body. /// eval/NativeContext 收集有效方法的全部能力返回约束;返回类型句柄仍为借用。 + /// 这是约束收集,不独立替代方法参数/覆盖或实际返回值校验;跨执行阶段保活由调用方负责。 pub fn method_trait_returns( &self, function: ObjectRef, diff --git a/crates/objects/src/native.rs b/crates/objects/src/native.rs index 8bd8fc1..b348c3d 100644 --- a/crates/objects/src/native.rs +++ b/crates/objects/src/native.rs @@ -9,6 +9,7 @@ use crate::{ObjectPayload, ObjectRef, ObjectStore, Value, VmError, WeakObjectRef /// Borrowed results produced inside a native call. Invoke delegates to another /// callable through the VM's existing frame loop, without recursively entering it. #[derive(Debug)] +/// 同一结果类型在回调返回时携带借用值,NativeContext::execute 成功输出后才携带已保留的输出根。 pub enum NativeResult { /// 回调内为借用结果;execute 成功输出时其中对象已获得移交给 VM 的根。 Return(Value), @@ -38,6 +39,7 @@ impl CallArity { Self { required, maximum } } /// VM/契约检查查询数量是否落在闭区间,不改变元数据。 + /// 本方法不校验上下界本身;需要诊断非法元数据时应先走 validate/check 或 Store 查询。 pub fn accepts(self, count: usize) -> bool { (self.required..=self.maximum).contains(&count) } @@ -73,6 +75,7 @@ pub enum CallEffect { ReadOnly, /// The native body itself is read-only; delegated effects are dynamic and /// checked by the VM in the same execution/heap scope. + /// 本体承诺只读;目标继续走同一作用域内的已发布效果检查和受控堆写屏障,不代表整条链已证明只读。 Delegated, /// 声明可能修改已有托管对象,只读执行不允许。 Mutating, @@ -177,6 +180,7 @@ impl CallType { /// Immutable native contract, including an explicit receiver where applicable. /// Missing fixed slots are covered by variadic; optionality comes from arity. #[derive(Debug, Clone)] +/// 宿主在构造阶段可填写公开字段;载荷发布后按不可变声明使用,不能私自改变已快照的定义引用。 pub struct CallSignature { /// 参数数量区间决定可省略的尾部参数,不由类型列表长度直接决定。 pub arity: CallArity, @@ -262,6 +266,7 @@ impl CallSignature { Ok(()) } /// 原生调用返回后校验结果类型;不重新验证整份参数元数据。 + /// 此处只检查借用 value,不取得结果拥有根;结果保留/移交由 NativeContext 或 VM 另行处理。 pub fn check_return( &self, store: &ObjectStore, @@ -276,11 +281,13 @@ impl CallSignature { pub trait NativeCallable: fmt::Debug + Send + Sync + std::any::Any { /// Immutable resolved types; referenced definitions must be traced by the /// payload. ContextNativeFunction snapshots and traces them automatically. + /// ContextNativeFunction::new 在构造时调用并保存快照;查询不得经内部可变性悄悄改变已发布的类型引用。 fn signature(&self) -> Option { None } /// Optional positional contract. Missing metadata remains an explicit legacy gap. + /// VM/NativeContext 查询数量;默认从 signature 推出区间,覆盖时需与已发布完整签名一致。 fn arity(&self) -> Option { self.signature().map(|signature| signature.arity) } @@ -329,7 +336,8 @@ impl<'a> NativeContext<'a> { self.store.implement_trait(definition, capability) } - /// 宿主只读查询存储,修改使用上下文受控入口。 + /// 宿主取得 &ObjectStore 查询视图,修改使用上下文受控入口。 + /// 这里只限制 Rust 借用权限,不自行开启 Heap 只读作用域;是否处于只读执行由调用入口决定。 pub fn store(&self) -> &ObjectStore { self.store } @@ -363,7 +371,7 @@ impl<'a> NativeContext<'a> { Ok(function) } - /// 创建空字典并登记其外部根。 + /// 创建空字典并登记上下文拥有的临时根;借出的身份不由回调手工 release。 pub fn new_dict(&mut self) -> Result { // 只有分配/升级成功后才登记根,错误路径不留下虚假的释放条目。 let object = self.store.new_dict()?; @@ -371,7 +379,7 @@ impl<'a> NativeContext<'a> { Ok(object) } - /// 创建空/已初始化 Cell 并登记外部根。 + /// 创建空/已初始化 Cell 并登记上下文临时根;若作为输出交付,execute 另保留一份输出根。 pub fn new_cell(&mut self, value: Option) -> Result { // 只有分配/升级成功后才登记根,错误路径不留下虚假的释放条目。 let object = self.store.new_cell(value)?; @@ -441,7 +449,10 @@ impl<'a> NativeContext<'a> { /// Internal execution bridge. Every returned object reference owns a root; /// consumers must release Return values or all Invoke arguments/function. - /// VM 执行桥:校验/保护输入,执行回调,验证直接结果,最后移交输出根。 + /// VM 的 invoke_native 调用此桥:校验/保护输入,执行回调,验证直接结果,最后移交输出根。 + /// VM 传入从 function 载荷克隆的分派器;手工调用需自行保证二者对应,本方法不验证 Arc 身份。 + /// Invoke 的最终返回约束由 VM 的 prepare_call/源码帧继续维护,不能把此结果当作最终调用完成。 + /// 此桥不推进源码委托,也不维护执行预算或回溯;普通覆盖检查选项由 VM 上层传递处理。 pub fn execute( // 整次回调独占借用存储,提供受控修改与查询。 store: &'a mut ObjectStore, @@ -569,6 +580,7 @@ impl ContextNativeFunction { } /// 宿主包装上下文回调,快照签名与显式捕获边,随后由 Store 分配。 + /// 构造本身不 retain 或校验托管句柄;allocate 时登记边,Store::native_signature/调用时再验证签名结构。 pub fn new( // 宿主静态诊断名,不承担语言类型身份。 name: &'static str, -- 2.52.0 From ca61378c928be29a3bb55743fc56a765ee298300 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 19:39:21 +0800 Subject: [PATCH 52/62] docs(objects): clarify type capability and effect query contracts --- crates/objects/src/effects.rs | 8 ++++-- crates/objects/src/types.rs | 49 ++++++++++++++++++++++++----------- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/crates/objects/src/effects.rs b/crates/objects/src/effects.rs index 45f6c46..fb0086b 100644 --- a/crates/objects/src/effects.rs +++ b/crates/objects/src/effects.rs @@ -1,5 +1,6 @@ //! Store::call_effect 的当前快照证明;None 表示无法证明,不代表确定有副作用。 //! 查询经过 Store 读取追踪,但不执行源码或分配托管对象;所有身份仅借用。 +//! 返回效果不会保留目标或冻结全局/Cell/方法表;缓存者需在追踪作用域收集观察并在使用前重验。 //! Snapshot proof of read-only call graphs. All identities are borrowed and all //! traversals iterative; analysis neither executes code nor allocates VM objects. use super::{AttributeLookup, BoundMethod}; @@ -10,8 +11,11 @@ use crate::{ use std::collections::{BTreeSet, VecDeque}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +/// infer_call_effect 队列与去重集合中的借用调用身份,read_only_callees 识别直接/绑定调用后构造。 struct Target { + /// 底层被调函数身份;队列本身不新增拥有根。 function: ObjectRef, + /// 已知的首个接收者,可用于解析 LoadFast(0) 和方法;未知时保持 None。 receiver: Option, } #[derive(Clone, Copy, PartialEq, Eq)] @@ -26,7 +30,7 @@ enum Fact { Method(Target), } impl Fact { - // 把实际值降为抽象身份事实,直接标量不保留其内容。 + // read_only_callees 对常量、全局值及 Cell 内容降为身份事实,直接标量不保留其内容。 fn value(value: Value) -> Self { // 对象保留借用身份,标量统一变成 Unknown;不把抽象事实当作实际求值结果。 match value { @@ -34,7 +38,7 @@ impl Fact { _ => Self::Unknown, } } - // 只从普通对象事实提取身份,不把方法对当作对象。 + // read_only_callees 的属性接收者/调用参数识别只提取普通对象身份,不把方法对当作对象。 fn object(self) -> Option { // 仅 Object 事实可用于属性接收者或调用身份,Unknown/Null/Method 都不冒充普通对象。 match self { diff --git a/crates/objects/src/types.rs b/crates/objects/src/types.rs index 487d6e8..35c9f3e 100644 --- a/crates/objects/src/types.rs +++ b/crates/objects/src/types.rs @@ -44,13 +44,17 @@ impl Drop for AttributeCheckGuard<'_> { } #[derive(Debug, Clone, Copy)] +/// 宿主在 new_type 中选择,源码 new_class 依父策略继承;其中对象句柄在类型分配后才由 trace 持边。 pub enum TypeConstructor { /// Allocate a plain, attribute-bearing instance of this type. + /// Construct 分配 Record,附请求类型,再按完整字段布局写入实参。 Record, /// Delegate to a managed callable. It must return an instance of this type. + /// 原实参直接委托宿主分配器;正确名义返回类型是分配器责任,Construct 本身不追加该返回检查。 Native(ObjectRef), /// Opt-in allocator inheritance. The requested nominal type is prepended /// to the constructor arguments; the allocator must create that real type. + /// 源码子类可沿用分配器,首参是实际请求 Type;分配器负责附着此身份,而非固定原生基类。 NativeTyped(ObjectRef), /// 禁止直接构造,源码派生沿用该策略后仍为抽象类型。 Abstract, @@ -61,12 +65,14 @@ pub struct ClassField { /// 字段存储名称,源码类创建时检查重复及保留名。 pub name: String, /// Source annotation resolved at checked construction/field writes. + /// 字段类型名文本,开启检查时按原声明类及其环境解析,不在解析源码时固定为全局句柄。 pub type_name: String, } /// Nominal definition with immutable identity/base and a managed method table. /// Its base, constructor and metatype are traced /// edges; no registry outside the heap keeps unused definitions alive. +/// 载荷 trace 登记基类等私有字段,元类型边由 EndorObject 的头部追踪,二者不重复登记。 #[derive(Debug, Clone)] pub struct EndorTypeObject { /// 诊断名称,名义身份仍由对象句柄决定。 @@ -210,6 +216,7 @@ impl NativeCallable for Construct { /// Each field owns one external root; the caller must release both. #[derive(Debug, Clone, Copy)] +/// bootstrap_types 的原始返回值携带两份拥有根;复制此 Copy 结构不 retain,不能按副本重复 release。 pub struct RootTypes { /// Object 根类型的一份外部拥有根,调用方须 release。 pub object: ObjectRef, @@ -311,7 +318,7 @@ impl ObjectStore { type_object: ObjectRef, ) -> Result<(), VmError> { self.payload::(type_object, "Type")?; - // 逐项登记已声明能力,重复引用也按拥有次数计算。 + // 逐项校验目标类型已有/继承能力对当前实例存储的约束;实际类型强边稍后由 Heap 登记。 for capability in self.inherited_traits(type_object)? { self.check_capability_storage(object, capability)?; } @@ -333,7 +340,7 @@ impl ObjectStore { "cyclic shared-reference type target", )); } - // 分配临时根后再附着类型/写字段,失败时上下文统一清理半成品。 + // 借用当前转发对象并记录其版本读取,尚未分配实例或增加任何拥有根。 let instance = self.object(current)?; // 有转发目标则继续,无目标时读取当前对象头部名义类型。 match instance.payload().type_target(self)? { @@ -361,7 +368,8 @@ impl ObjectStore { Ok(false) } - /// VM/宿主判断实例有效类型是否为 expected 的子类型,无类型旧对象返回 false。 + /// VM/宿主判断实例有效类型是否为 expected 的子类型,有效但无名义类型的旧对象返回 false。 + /// 异堆或失效句柄仍报错,不能把错误当作普通不匹配。 pub fn is_instance(&self, object: ObjectRef, expected: ObjectRef) -> Result { self.payload::(expected, "Type")?; // 有效类型可来自转发对象;无类型不是 InvalidHandle,而是正常 false。 @@ -373,6 +381,7 @@ impl ObjectStore { /// Updating the managed method table preserves nominal identity and base. /// 宿主/VM 受控更新方法表,身份/基类不变,先验证覆盖及能力约束再替换载荷。 + /// 方法对象需为有效句柄;无相关契约时此处不单独证明可调用性,实际调用仍由 VM 分派。 pub fn define_method( &mut self, type_object: ObjectRef, @@ -382,7 +391,7 @@ impl ObjectStore { self.object(function)?; // 持有名称用于新载荷与诊断,后续校验借用该字符串。 let name = name.into(); - // 类型名称不允许空串,但身份判断仍使用句柄而非文本。 + // 方法名称不能为空或占用保留属性 type;这不是类型声明名校验。 if name.is_empty() || name == "type" { return Err(VmError::InvalidArgument("reserved or empty method name")); } @@ -473,7 +482,7 @@ impl ObjectStore { // 方法提交前验证函数句柄,拒绝与完整继承字段序列重名。 for (name, method) in &methods { self.object(*method)?; - // 类型名称不允许空串,但身份判断仍使用句柄而非文本。 + // 方法名不能为空、占用 type 或与字段冲突;完整可调用契约在对应能力/覆盖检查中核对。 if name.is_empty() || name == "type" || fields.iter().any(|field| field.name == *name) { return Err(VmError::InvalidArgument( "method conflicts with class field", @@ -607,6 +616,7 @@ impl ObjectStore { /// This query follows current globals, closure Cells and known receivers without /// executing code, freezing targets or retaining a cross-query cache. /// VM/契约通过 effects 分析当前调用图快照,None 表示无法证明整体效果。 + /// 结果不冻结目标或提供跨次缓存;追踪到的读取只在当前读取作用域收集,后续变更需重新证明。 pub fn call_effect(&self, function: ObjectRef) -> Result, VmError> { self.infer_call_effect(function) } @@ -785,7 +795,7 @@ impl ObjectPayload for BoundMethod { fn is_function(&self) -> bool { true } - /// 绑定方法/能力定义不实现原地赋值,允许只读局部重绑定引用。 + /// 绑定方法不实现原地赋值,允许只读局部重绑定引用。 fn reference_assignment_only(&self) -> bool { true } @@ -836,20 +846,27 @@ impl NativeCallable for CallBoundMethod { /// Use TypedMethodRequirement for parameter/return/effect contracts. #[derive(Debug, Clone, PartialEq, Eq)] pub struct MethodRequirement { + /// 能力要求存在的方法名,new_trait 检查非空、保留名及重复。 pub name: String, + /// 不含 self 的位置实参数量;实现的区间需接受此数量加一,不要求区间本身是精确数量。 pub parameters: usize, } /// Typed capability requirement, including the explicit receiver slot. #[derive(Debug, Clone)] pub struct TypedMethodRequirement { + /// 完整契约所属方法名,new_typed_trait 按名保存并拒绝重复。 pub name: String, + /// 含显式接收者的固定数量签名,参数/返回类型及效果按替换规则验证。 pub signature: crate::CallSignature, } #[derive(Debug)] /// 显式能力定义:最小数量要求与可选完整签名同属托管载荷,定义本身不可任意替换。 pub struct EndorTraitObject { + /// 诊断名称,能力相等仍以对象身份判断。 name: String, + /// 最小数量要求;类型化能力也从规范签名派生这份兼容视图。 requirements: Vec, + /// 按方法名保存的完整类型/效果契约,签名中的定义引用由本载荷 trace 保活。 signatures: BTreeMap, } impl EndorTraitObject { @@ -867,7 +884,7 @@ impl EndorTraitObject { } } impl ObjectPayload for EndorTraitObject { - /// Heap 分配/更新载荷时枚举拥有边,重复边仍逐条报告。 + /// Heap 分配能力载荷时枚举签名拥有边,重复边仍逐条报告。 fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { // 能力完整签名的全部类型定义参与 trace,不只看当前调用到的分支。 for signature in self.signatures.values() { @@ -877,7 +894,7 @@ impl ObjectPayload for EndorTraitObject { } } } - /// 绑定方法/能力定义不实现原地赋值,允许只读局部重绑定引用。 + /// 能力定义不实现原地赋值,允许只读局部重绑定引用。 fn reference_assignment_only(&self) -> bool { true } @@ -1193,7 +1210,7 @@ impl ObjectStore { self.payload::(capability, "Trait")?; // 显式声明或继承才算支持能力,方法形状相同也不会自动成为声明。 let declared = self.inherited_traits(definition)?.contains(&capability); - // 命中声明后重验当前方法/实例,避免把历史声明当作永久有效证明。 + // 命中声明后重验类型的方法契约;此 API 不检查某个实例存储,值级查询另经 supports。 if declared { self.validate_trait(definition, capability, false)?; } @@ -1300,7 +1317,8 @@ impl ObjectStore { /// Method names whose instance storage participates in typed capability /// validity. Hosts can subscribe to these attribute keys when caching. - /// 宿主缓存查询当前类型化能力涉及的属性键,返回去重名称,不建立订阅。 + /// 宿主缓存(如 Tessera 快照检查)查询当前类型化能力涉及的属性键,返回去重名称,不建立订阅。 + /// 名称枚举本身不证明实例存储合规;绕过属性查找分派前另调用 check_typed_instance_storage。 pub fn typed_method_names(&self, object: ObjectRef) -> Result, VmError> { // 按名称去重,多个能力共用方法名只返回一次。 let mut names = std::collections::BTreeSet::new(); @@ -1456,7 +1474,8 @@ impl ObjectStore { /// NativeTyped allocators can validate their field suffix before any effects. /// Count is always checked; annotation matching follows the opt-in setting. - /// Record/NativeTyped 构造器在副作用前校验字段后缀;数量始终检查,类型匹配由开关控制。 + /// Record/NativeTyped 分配器用它检查完整声明字段值列表(含继承字段),不包含分配器自己的额外参数。 + /// 数量始终检查,类型匹配由开关控制;应在分配或其他副作用前显式调用。 pub fn check_class_field_arguments( &self, definition: ObjectRef, @@ -1506,7 +1525,7 @@ impl ObjectStore { } else { return Err(VmError::UnknownAnnotation(field.type_name.clone())); }; - // 类型化能力才需要扫描实例存储,最小数量能力没有遮蔽签名要求。 + // 按刚解析的字段约束匹配当前值,失败时报告字段位置与实际类型,不提交任何写入。 if !self.matches_annotation(value, expected)? { return Err(VmError::TypeMismatch { function: definition.name.clone(), @@ -1525,7 +1544,7 @@ impl ObjectStore { name: &str, receiver: Option, ) -> Result { - // 先验证定义/函数载荷,再访问字段表或声明环境。 + // 先验证源码函数载荷,后续从其代码签名、全局和已捕获内置字典解析注解。 let data = self.payload::(function, "function")?; // 方法 owner 解析需要代码中的源码声明元数据,函数本身只持有代码引用。 let signature = self @@ -1566,7 +1585,7 @@ impl ObjectStore { }); // 普通名称必须指向 Type 或 Trait 载荷,其他值不能充当类型注解。 if let Some(Value::Object(definition)) = value { - // 查询原对象固有属性身份,而不是只查看转发终点。 + // 按环境绑定值的实际载荷确认它是 Type 或 Trait 定义,不把普通对象当作注解。 let payload = self.object(definition)?.payload(); // 识别名义类型,描述符只借用该定义身份。 if payload.as_any().is::() { @@ -1641,7 +1660,7 @@ impl ObjectStore { } /// 类型错误诊断格式化预期名称;名义/能力定义仍验证句柄和载荷。 pub fn annotation_type_name(&self, expected: AnnotationType) -> Result { - // 直接类别与名义/能力匹配分开;Type/Function 类别不等于任意可调用对象。 + // 内建类别使用固定显示名,名义/能力注解从有效定义载荷取名,不在这里匹配实际值。 Ok(match expected { AnnotationType::Any => "Object".into(), AnnotationType::Unit => "Unit".into(), -- 2.52.0 From 262edf435186deb72c0eb514131a690d05d90bfd Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 19:40:52 +0800 Subject: [PATCH 53/62] docs(objects): clarify numeric conversion and wordcode boundaries --- crates/objects/src/instruction.rs | 4 +++- crates/objects/src/number.rs | 25 ++++++++++++++++++------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/crates/objects/src/instruction.rs b/crates/objects/src/instruction.rs index b46219d..54883af 100644 --- a/crates/objects/src/instruction.rs +++ b/crates/objects/src/instruction.rs @@ -152,7 +152,7 @@ impl Instruction { pub const fn new(opcode: Opcode, argument: u32) -> Self { Self { opcode, argument } } - /// 编译器发出无参数指令的便捷入口,把 argument 明确置零。 + /// 编译器发出无参数指令的便捷入口,把 argument 明确置零;此处不检查 opcode 是否真的无参数。 pub const fn simple(opcode: Opcode) -> Self { Self::new(opcode, 0) } @@ -195,6 +195,7 @@ impl Instruction { /// EndorCode::from_instructions 调用:小操作数直接编码,大操作数最多用三个前缀。 /// 每单元两字节,前缀携带高位;不在这里重算跳转距离或校验常量/槽索引。 + /// 空输入可编码为空字节串,EndorCode 的后续解码才拒绝空代码,编码成功不等于可执行。 pub fn encode_all(instructions: &[Self]) -> Result, VmError> { // 逐条追加;报错时局部缓冲丢弃,不向调用者返回部分编码。 let mut bytes = Vec::new(); @@ -222,6 +223,7 @@ impl Instruction { } } +/// 解码器产出、代码校验器和 eval 消费的逻辑位置;公开字段本身不保证由验证过的代码产生。 #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct DecodedInstruction { /// 逻辑指令起点(包括首个前缀),单位是双字节代码单元。 diff --git a/crates/objects/src/number.rs b/crates/objects/src/number.rs index 0def382..ddf5527 100644 --- a/crates/objects/src/number.rs +++ b/crates/objects/src/number.rs @@ -10,6 +10,7 @@ use std::fmt; use crate::{CompareOp, ObjectPayload, ObjectRef, VmError}; #[derive(Debug, Clone, PartialEq, Eq)] +/// 编译器字面量和精确运算的规范整数;仅保存符号和数字,构造不执行算术预算。 pub struct EndorInteger { /// 规范化整数符号,零为 false。 negative: bool, @@ -80,6 +81,7 @@ impl fmt::Display for EndorInteger { } #[derive(Debug, Clone, PartialEq, Eq)] +/// 编译器保留原拼写的十进制值;表示相等会区分 1.0 与 1.00,数学比较另走 numeric_compare。 pub struct EndorDecimal { /// 已验证但未规范化的原十进制字面量。 literal: String, @@ -160,7 +162,7 @@ pub struct EndorRational { impl EndorRational { /// Normalizes denominator sign, without reducing or rounding the fraction. - /// 宿主/Store 构造当前数值表示或载荷,具体不变量见下方构造步骤。 + /// EndorNumber::rational 把已解析的整数交到这里;只校正分母符号,不执行约分或运算预算。 pub fn new( mut numerator: EndorInteger, mut denominator: EndorInteger, @@ -204,8 +206,11 @@ impl fmt::Display for EndorRational { /// Internal storage forms within one Endor number API, not language classes. #[derive(Debug, Clone, PartialEq, Eq)] pub enum EndorReal { + /// 已规范符号和前导零的整数。 Integer(EndorInteger), + /// 保留符号、尾零和指数原拼写的十进制值。 Decimal(EndorDecimal), + /// 正分母的精确分数;构造阶段不保证已约分。 Rational(EndorRational), /// IEEE-754 bits preserve signed zero, infinities and NaN payloads. BinaryFloat(u64), @@ -242,7 +247,9 @@ impl fmt::Display for EndorReal { /// Language-level comparison and hashing are not defined by these Rust traits. #[derive(Debug, Clone, PartialEq, Eq)] pub enum EndorNumber { + /// VM 算术、比较和图形收窄接受的实数表示。 Real(EndorReal), + /// 可保存、显示并判断真值;当前算术与数学比较均拒绝此变体。 Complex { /// 复数实部分量。 real: EndorReal, @@ -334,13 +341,14 @@ impl fmt::Display for EndorNumber { } #[derive(Debug, Clone)] +/// Store::new_number 使用的堆载荷;与直接 Value::Number 共用数值语义,但需遵守托管句柄所有权。 pub struct EndorNumberObject { /// 拥有数值表示,不含托管对象强边。 value: EndorNumber, } impl EndorNumberObject { - /// 宿主/Store 构造当前数值表示或载荷,具体不变量见下方构造步骤。 + /// Store::new_number 在托管分配前包装数值;本方法不分配堆槽,也不新增外部根。 pub fn new(value: EndorNumber) -> Self { Self { value } } @@ -444,7 +452,8 @@ impl EndorNumber { } /// Explicit narrowing for graphics/FFI boundaries; exact values themselves /// retain their representation. Non-real or non-finite values are rejected. - /// 宿主图形/FFI 显式收窄,拒绝复数、非有限值和不可转换范围。 + /// Tessera 图形桥在宿主边界显式收窄,结果可能舍入,不承诺精确往返;拒绝复数、非有限值和超范围结果。 + /// 精确侧转换也受 exact_real 的文本/指数预算限制,能保存的字面量不一定能在此转换。 pub fn to_f64(&self) -> Result { use num_traits::ToPrimitive; // 先要求 Real,不能静默丢弃复数虚部。 @@ -465,7 +474,8 @@ impl EndorNumber { } Ok(value) } - /// eval 二元算术:i64 安全快路径,任一浮点选择 float,否则精确有理数运算。 + /// eval 的 BinaryArithmetic 使用:i64 安全快路径,任一浮点选择 float,否则精确有理数运算。 + /// 精确运算受输入预算和结果 16384 位上限限制;成功结果规范为 Integer 或约分 Rational。 pub fn arithmetic(&self, other: &Self, op: ArithmeticOp) -> Result { // 双方为可容纳 i64 的 Integer 才用快路径;运算溢出回退,不截断。 if let (Some(a), Some(b)) = (self.as_i64(), other.as_i64()) { @@ -506,7 +516,7 @@ impl EndorNumber { )) } }; - // 先要求两个输入均为实数;后续按精确/浮点模式转换。 + // 将已确认的两个实数转换为浮点,精确侧在这里可能因预算或范围失败。 let (a, b) = (convert(a)?, convert(b)?); // 两种运算模式都拒绝零除,包括浮点正负零。 if matches!(op, ArithmeticOp::Divide) && b == 0. { @@ -525,9 +535,9 @@ impl EndorNumber { } return Ok(Self::binary_float(result)); } - // 先要求两个输入均为实数;后续按精确/浮点模式转换。 + // 无浮点操作数时转为精确分数;转换先检查各表示的输入预算。 let (a, b) = (exact_real(a)?, exact_real(b)?); - // 两种运算模式都拒绝零除,包括浮点正负零。 + // 精确除法同样拒绝零分母,零的不同精确表示已在转换后统一。 if matches!(op, ArithmeticOp::Divide) && b.is_zero() { return Err(VmError::InvalidArgument("division by zero")); } @@ -552,6 +562,7 @@ impl EndorNumber { } } /// eval/数值载荷数学比较,拒绝复数,处理 NaN 无序与无穷,不依赖 Rust Eq。 + /// 有限混合比较把二进制浮点精确转为分数,不先舍入另一侧;精确转换仍可能因输入预算报错。 pub fn numeric_compare(&self, other: &Self, op: CompareOp) -> Result { use std::cmp::Ordering; // 先要求两个输入均为实数;后续按精确/浮点模式转换。 -- 2.52.0 From ca00c1d1107a3816ba99d9b6d9263db8c62d53a4 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 19:41:49 +0800 Subject: [PATCH 54/62] docs(objects): explain read observation lifetime and equality --- crates/objects/src/heap.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/objects/src/heap.rs b/crates/objects/src/heap.rs index 8bdc255..3e80595 100644 --- a/crates/objects/src/heap.rs +++ b/crates/objects/src/heap.rs @@ -106,6 +106,7 @@ pub enum ReadDependency { object: ObjectRef, }, /// 指定文本名称的命中/缺失值快照;None 与 Some(Nil) 区分。 + /// Store 重验使用 Value 的表示相等;其中对象按句柄身份比较,不递归验证被引用对象内容。 Name { /// 接受名称查找的字典身份。 object: ObjectRef, @@ -129,10 +130,15 @@ impl ReadDependency { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] /// 精确依赖的排序/去重键;与 ReadDependency 分开,观察值不参与键身份。 enum ReadKey { + /// 整对象观察按完整身份去重。 Object(ObjectRef), + /// 字典种类观察与同对象的版本/名称观察分别保留。 DictionaryKind(ObjectRef), + /// 同一字典的不同文本键各自保存首次查找值。 Name(ObjectRef, String), } +/// Store::end_read_tracking_precise 返回的子调用观察;结束子区间不结束外层只读作用域。 +/// 列表中的身份均借用,内容读取优先于验证分类;只包含该作用域进入前已有对象的已登记读取。 #[derive(Debug)] pub struct TrackedReads { /// 子区间对象读取与用途分类。 @@ -140,6 +146,8 @@ pub struct TrackedReads { /// 精确观察列表,供 Store::read_dependency_valid 重验。 pub dependencies: Vec, } +/// Store::end_read_only_precise 和 VM 精确只读调用返回的整个作用域观察。 +/// 对象/键分别去重且保留首次快照,不保活观察目标;缓存使用者通过 Store 重验依赖。 #[derive(Debug)] pub struct ReadSet { /// 整个作用域读取到的借用对象身份。 -- 2.52.0 From d8f195e907bb7a22e70fcd5a2717fbf1ff33074e Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 19:44:07 +0800 Subject: [PATCH 55/62] docs(frontend): reconcile AST guide with control flow and schema notes --- crates/frontend/src/lexer.rs | 2 +- docs/runtime/ast.md | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/crates/frontend/src/lexer.rs b/crates/frontend/src/lexer.rs index a4e75ae..fdc9532 100644 --- a/crates/frontend/src/lexer.rs +++ b/crates/frontend/src/lexer.rs @@ -1,4 +1,4 @@ -//! parser 及宿主分词入口;每次交给 PEG 一个剩余源码前缀,再把相对跨度换回原文绝对字节位置。 +//! parser 及宿主分词入口;每次交给 PEG 从游标起的剩余源码后缀,再把相对跨度换回原文绝对字节位置。 use crate::{Diagnostic, Span, Token, TokenKind}; /// Lex a reader snapshot and preserve provenance in any diagnostic. diff --git a/docs/runtime/ast.md b/docs/runtime/ast.md index 8627900..b66a047 100644 --- a/docs/runtime/ast.md +++ b/docs/runtime/ast.md @@ -25,6 +25,10 @@ ASDL 支持 module、产品类型 `(Type field, ...)`、构造器联合 `A(...) Rust 用 struct/enum、Vec/Option 表达结构;生成器在按值依赖图的回边放置 Box,Vec 已经断开按值递归。增加节点后,Rust 类型、两种语言的遍历接口和 JSON Schema 由同一次生成更新。 +中文节点、字段和构造器说明维护在 [generate_ast.py](../../scripts/generate_ast.py) 的 `TYPE_NOTES`、`FIELD_NOTES`、`VARIANT_NOTES` 中,由模板写入 Rust 和 TypeScript 源码。字段说明按所属类型区分,`span` 统一说明原文定位;访问器同时说明遍历顺序、失败退出和子树跳过规则。阅读 schema 时可对照 [Rust 节点](../../crates/frontend/src/ast/generated.rs)、[Rust 遍历器](../../crates/frontend/src/ast/visitor.rs) 或 [TypeScript 接口](../../crates/frontend/editor/ast.ts)。更改这些说明后也应运行生成命令,手改派生文件会在下次生成时丢失。 + +本次注释补充保留 `Endor.asdl` 原文:其注释和空白也参与下述协议身份计算,直接添加中文注释会令已有 JSON 文档的 schema 标识失配。中文说明因此随生成源码维护;ASDL 仍是唯一结构定义源,说明字典不新增节点或改变协议结构。 + ## 根、声明、语句、表达式 | 层级 | 当前数据模型 | @@ -33,15 +37,16 @@ Rust 用 struct/enum、Vec/Option 表达结构;生成器在按值依赖图 | Program | declarations: Vec 与 span;声明种类为 Function、Class | | Decl/DeclKind | 声明的位置及其具体种类;Function 保存参数、返回类型名称和 Block;Class 保存名称、可选基类、字段及方法 | | Block | statements: Vec 与 span,不再用固定的 bindings/result 字段表示 | -| Stmt/StmtKind | Binding、Assignment、MemberAssignment、Return、Expression、Declaration;每条语句有自身范围 | +| Stmt/StmtKind | Binding、Assignment、MemberAssignment、If、While、Return、Expression、Declaration;每条语句有自身范围 | +| IfStatement/WhileStatement | 条件和嵌套 Block;if 另有可选 otherwise,while 每轮重新求条件 | | MemberAssignment | 对象表达式、属性名称、右侧表达式与原文范围 | | Assignment | 名称目标、右侧表达式及原文范围;独立于 let 的 Binding 节点 | | ReturnStatement | 可选表达式与范围;当前源码中的 return 仍要求表达式 | -| Expr/ExprKind | 名称、数字原文、字符串、符号、一元/二元运算、成员和调用 | +| Expr/ExprKind | 名称、数字原文、字符串、符号、等待、一元/二元运算、成员和调用 | 参数与返回类型仍为 Name,只保留语法,不实施类型或名称检查。Program::functions() 提供函数迭代便捷接口。旧的 Program.functions、Block.bindings/result/return_span 字段已移除;源码构造和使用方改为声明/语句列表。ExprKind 的既有表达式结构保留。 -AST 数据模型允许一般语句列表,不代表 parser 接受了所有组合。模块要求一个或多个函数/类声明;函数体接受 let/名称赋值/属性赋值/表达式语句,以及可选的末尾带值 return。Interactive 模式接受一次函数/类声明、let 绑定、名称/属性赋值或表达式,必须消费完整输入;不创建交互会话,不处理续行提示,不执行语言代码。多条交互提交由宿主分别解析。条件、循环、导入、trait 等尚未实现,也不加入 FunctionType 这种 Python 专用入口。 +AST 数据模型允许一般语句列表,不代表 parser 接受了所有组合。模块要求一个或多个函数/类声明;函数体接受 let/名称赋值/属性赋值/表达式语句、if/else 和 while,以及各块可选的末尾带值 return。Interactive 模式解析一次函数/类声明、控制流、let 绑定、名称/属性赋值或表达式,必须消费完整输入;不创建交互会话,不处理续行提示,不执行语言代码。多条交互提交由宿主分别解析。函数和交互根的条件、循环均有对应名称/初始化分析与跳转指令生成;具体 AST 仍需通过后续语义及字节码校验。导入和 trait 声明尚无源码语法,也没有 FunctionType 这种 Python 专用入口。 ## 解析与位置 -- 2.52.0 From a275d6ee559574dc7748ca51a7e7b60f9f1aa1c1 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 19:46:25 +0800 Subject: [PATCH 56/62] docs(compiler): clarify analysis and template loading contracts --- crates/compiler/src/analysis.rs | 11 +++++++---- crates/compiler/src/codegen.rs | 6 ++++-- crates/compiler/src/lib.rs | 3 ++- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/crates/compiler/src/analysis.rs b/crates/compiler/src/analysis.rs index 1c01e32..73379c2 100644 --- a/crates/compiler/src/analysis.rs +++ b/crates/compiler/src/analysis.rs @@ -33,7 +33,7 @@ pub enum NameBinding { #[derive(Debug, Clone, PartialEq, Eq)] /// 一次名称表达式的解析结果;名字加源码跨度定位具体使用点,避免同名出现互相混淆。 pub struct NameUse { - // 名称文本;Scope 使用它作为代码名,NameUse 使用它标识被读取的名称。 + // 本次读取的名称文本,生成器与 span 联合匹配,取得该表达式的绑定。 pub name: String, // 原源码中的半开字节范围,用于诊断及生成阶段定位。 pub span: Span, @@ -46,7 +46,7 @@ pub struct NameUse { pub struct Scope { // 决定赋值存局部还是全局,以及是否允许 return。 pub kind: ScopeKind, - // 名称文本;Scope 使用它作为代码名,NameUse 使用它标识被读取的名称。 + // 根的合成诊断名或源码函数名;生成器同时用 span 区分不同类的同名方法。 pub name: String, // 原源码中的半开字节范围,用于诊断及生成阶段定位。 pub span: Span, @@ -57,7 +57,8 @@ pub struct Scope { } #[derive(Debug, Clone)] -/// compile_ast 把整个解析结果交给 codegen;模板生成不再自行猜测名称绑定。 +/// analyze 返回、compile_ast 交给 codegen 的分析结果;根布局和每个函数体布局分开保存。 +/// 字段公开以供宿主/测试检查,但改动此表不会更新原 AST,也没有公开入口把任意改表直接编译。 pub struct SymbolTable { // 根代码单元,即模块、表达式或交互输入的作用域。 pub root: Scope, @@ -86,6 +87,8 @@ fn declaration_name(declaration: &Decl) -> &frontend::ast::Name { /// shadows the global for the entire function, but cannot be read early. /// compile_ast 和直接分析测试调用;options 仅声明额外全局名,返回根及各函数的绑定布局。 /// 函数体先收集所有局部名,因此后面的 let 也会遮蔽前面的全局读取,但未初始化读取会报错。 +/// 直接调用只做本阶段的名称/控制流分析,不执行 AstDocument 跨度验证、类型解析或字节码验证; +/// 需要完整流水线的宿主使用 compile_ast/compile_source,不能把此处成功视为代码已可执行。 pub fn analyze(root: &AstRoot, options: &CompileOptions) -> Result { // 交互模式要求恰好一条语句;空输入或批量输入不是本编译入口支持的单位。 if let AstRoot::Interactive(input) = root @@ -96,7 +99,7 @@ pub fn analyze(root: &AstRoot, options: &CompileOptions) -> Result = options.globals.iter().cloned().collect(); // 在分析任何代码前拒绝空宿主名,避免不可解析的名称混入绑定集合。 if globals.contains("") { diff --git a/crates/compiler/src/codegen.rs b/crates/compiler/src/codegen.rs index 50b4789..801d19c 100644 --- a/crates/compiler/src/codegen.rs +++ b/crates/compiler/src/codegen.rs @@ -21,7 +21,7 @@ pub struct CompiledCode { } impl CompiledCode { - /// 宿主/测试检查已解码指令及偏移,借用模板,不执行代码。 + /// 宿主/测试检查已解码指令及偏移,借用模板,不执行代码;子代码常量此时仍为 Nil 占位。 pub fn instructions(&self) -> &[DecodedInstruction] { self.code.instructions() } @@ -55,6 +55,8 @@ impl CompiledCode { /// released on both success and failure, including partial recursive loads. /// 宿主/测试显式装入目标 VM;成功返回一个拥有代码根,调用者执行完成后负责 release。 /// 已分配子根无论后续成功或失败都在外层归还,父代码成功分配时已有自己的常量内部边。 + /// Tessera Candidate 或开发者页面安装器持有这份代码根;本方法不运行模块、不安装 globals, + /// 每次 load 重新分配目标堆身份,模板可复用,调用者仍需显式执行并清理返回值。 pub fn load(&self, vm: &mut Vm) -> Result { // 记录本次已成功加载的子代码拥有根,外层统一归还,覆盖递归中途失败。 let mut roots = Vec::new(); @@ -483,7 +485,7 @@ impl<'a> Emitter<'a> { /// 数值字面量和已合并符号的字面量共用,直接从十进制拼写构造精确数值,不经过 f64。 fn number(&mut self, spelling: &str, span: Span) -> Result<(), Diagnostic> { - // 根据拼写中的小数点选择精确十进制或整数解析,失败保留字面量跨度。 + // 当前源码数字不含指数,按小数点选择精确十进制或整数解析;手工 AST 拼写也走此选择而不重分词。 let value = if spelling.contains('.') { EndorNumber::decimal(spelling) } else { diff --git a/crates/compiler/src/lib.rs b/crates/compiler/src/lib.rs index 83a787f..0aeb08d 100644 --- a/crates/compiler/src/lib.rs +++ b/crates/compiler/src/lib.rs @@ -39,7 +39,8 @@ pub fn compile_source( compile_ast(&root, source, options).map_err(|error| source.annotate(error)) } -/// compile_source 及已有 AST 的宿主/测试调用;先验证结构与跨度,再分析名称,最后输出代码模板。 +/// compile_source、Tessera 源码计划/开发者页面安装器及已有 AST 的宿主/测试调用; +/// 先验证结构与跨度,再分析名称,最后输出代码模板。 /// 传入 source 必须是期望匹配的位置快照;跨度有效只说明位置在范围内,不能证明 AST 与文本语义相同。 /// Compile an AST against its original source snapshot. Span validation does /// not prove the AST's semantic equivalence to the supplied text. -- 2.52.0 From f70924b4a7555cb6c50c96c0b1c14c9c7f3ad11d Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 19:48:11 +0800 Subject: [PATCH 57/62] docs(frontend): specialize single-payload AST field explanations --- crates/frontend/editor/ast.ts | 32 ++++++++++++++++---------------- scripts/generate_ast.py | 12 +++++++++++- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/crates/frontend/editor/ast.ts b/crates/frontend/editor/ast.ts index 9b2982a..8bc7387 100644 --- a/crates/frontend/editor/ast.ts +++ b/crates/frontend/editor/ast.ts @@ -10,11 +10,11 @@ export interface Span { start: number; end: number; } export type AstDocument = { schema: typeof AST_SCHEMA_ID; root: AstRoot; }; // parse_ast 按模式构造的模块、表达式或交互根,compiler 据此选择生成入口。 // Module: 模块根,包含函数/类声明。 -// Module.body: body 保存 Program 类型的值。 +// Module.body: 当前模式选中的模块、表达式或交互根载荷,compiler 按对应构造器生成入口代码。 // Expression: 表达式根,编译后直接返回求值结果。 -// Expression.body: body 保存 Expr 类型的值。 +// Expression.body: 当前模式选中的模块、表达式或交互根载荷,compiler 按对应构造器生成入口代码。 // Interactive: 交互根,包含一次完整提交。 -// Interactive.body: body 保存 InteractiveInput 类型的值。 +// Interactive.body: 当前模式选中的模块、表达式或交互根载荷,compiler 按对应构造器生成入口代码。 export type AstRoot = { kind: "Module"; data: Program; } | { kind: "Expression"; data: Expr; } | { kind: "Interactive"; data: InteractiveInput; }; // parser 复制的名称文本与位置,用于声明、类型注解或属性名。 // text: 拥有名称文本,不依赖输入缓冲存活。 @@ -30,9 +30,9 @@ export type Program = { declarations: Array; span: Span; }; export type Decl = { kind: DeclKind; span: Span; }; // 函数与类声明的判别载荷,外层 Decl 另外保存完整跨度。 // Function: 显式参数/返回注解与函数体,compiler 生成独立模板及源码签名;解析本身不调用函数。 -// Function.definition: definition 保存 Function 类型的值。 +// Function.definition: 函数或类的完整声明,包含其名称、成员/参数及原文范围。 // Class: 类名、可选基类、字段和方法,compiler 检查成员后生成构类指令。 -// Class.definition: definition 保存 Class 类型的值。 +// Class.definition: 函数或类的完整声明,包含其名称、成员/参数及原文范围。 export type DeclKind = { kind: "Function"; data: Function; } | { kind: "Class"; data: Class; }; // 类名、可选基类、字段和方法,compiler 检查成员后生成构类指令。 // name: 类声明名与精确位置。 @@ -63,21 +63,21 @@ export type Block = { statements: Array; span: Span; }; export type Stmt = { kind: StmtKind; span: Span; }; // 语句判别载荷,parser 选择分支,compiler 按绑定、赋值、控制流等规则处理。 // Binding: let 新绑定,compiler 检查重名并分析右值后才标记目标已初始化。 -// Binding.binding: binding 保存 Binding 类型的值。 +// Binding.binding: let 新绑定,compiler 先分析/求右值,再登记目标初始化。 // Assignment: 普通名称赋值,compiler 按作用域写局部槽或全局环境。 -// Assignment.assignment: assignment 保存 Assignment 类型的值。 +// Assignment.assignment: 普通名称或成员赋值的完整节点,compiler 据构造器选择槽写入或属性写入。 // MemberAssignment: 成员写入的对象、属性名与右值,compiler 生成 StoreAttr。 -// MemberAssignment.assignment: assignment 保存 MemberAssignment 类型的值。 +// MemberAssignment.assignment: 普通名称或成员赋值的完整节点,compiler 据构造器选择槽写入或属性写入。 // If: 条件、then 和可选 else,compiler 合并继续路径的初始化集合并生成跳转。 -// If.statement: statement 保存 IfStatement 类型的值。 +// If.statement: 条件、循环或返回节点,compiler 按构造器处理路径、跳转或帧出口。 // While: 循环条件与体,compiler 生成回边并处理循环可能零次执行。 -// While.statement: statement 保存 WhileStatement 类型的值。 +// While.statement: 条件、循环或返回节点,compiler 按构造器处理路径、跳转或帧出口。 // Return: 函数返回,compiler 对缺省值补 Nil;当前源码语法要求 return 后有表达式。 -// Return.statement: statement 保存 ReturnStatement 类型的值。 +// Return.statement: 条件、循环或返回节点,compiler 按构造器处理路径、跳转或帧出口。 // Expression: 表达式载荷及完整范围,compiler 生成留下一个结果的求值序列。 -// Expression.expression: expression 保存 Expr 类型的值。 +// Expression.expression: 语句位置的表达式;函数块丢弃结果,交互根可把它作为返回值。 // Declaration: 顶层/交互声明的种类及完整范围,compiler 按函数或类分派。 -// Declaration.declaration: declaration 保存 Decl 类型的值。 +// Declaration.declaration: 交互根中的函数/类声明,compiler 预登记名称;嵌套声明当前被拒绝。 export type StmtKind = { kind: "Binding"; data: Binding; } | { kind: "Assignment"; data: Assignment; } | { kind: "MemberAssignment"; data: MemberAssignment; } | { kind: "If"; data: IfStatement; } | { kind: "While"; data: WhileStatement; } | { kind: "Return"; data: ReturnStatement; } | { kind: "Expression"; data: Expr; } | { kind: "Declaration"; data: Decl; }; // let 新绑定,compiler 检查重名并分析右值后才标记目标已初始化。 // name: let 目标名,重复声明由语义分析拒绝。 @@ -120,11 +120,11 @@ export type InteractiveInput = { statements: Array; span: Span; }; export type Expr = { kind: ExprKind; span: Span; }; // 名称、字面量、等待、运算和调用等表达式载荷,范围由外层 Expr 提供。 // Name: 变量引用文本,compiler 决定局部槽或全局索引。 -// Name.text: text 保存 string 类型的值。 +// Name.text: 名称引用原文,由 compiler 名称分析解析到局部槽或全局名字表。 // Number: 原始十进制拼写,保留全部位数、前导零及尾零,不转浮点。 -// Number.spelling: spelling 保存 string 类型的值。 +// Number.spelling: 原始数字拼写,compiler 构造精确数值;不把文本先转为二进制浮点。 // String: 已解码受支持转义的拥有文本,原引号范围在外层 Expr。 -// String.value: value 保存 string 类型的值。 +// String.value: 字符串解码后的拥有文本,原引号和转义仍可通过外层范围读取。 // Symbol: 点开头的符号名,前端可表示,当前 compiler 拒绝生成运行时值。 // Symbol.name: 成员或符号名,不是已解析的运行时属性句柄。 // Await: 等待子表达式,VM 执行上下文决定是否允许停放。 diff --git a/scripts/generate_ast.py b/scripts/generate_ast.py index 54611d7..45b38bd 100644 --- a/scripts/generate_ast.py +++ b/scripts/generate_ast.py @@ -53,6 +53,16 @@ TYPE_NOTES = { FIELD_NOTES = { ("AstDocument", "schema"): "协议身份,wire 按 AST_SCHEMA_ID 核对;不是源码内容哈希。", ("AstDocument", "root"): "拥有的语法根,文档校验及编译从这里递归。", + ("AstRoot", "body"): "当前模式选中的模块、表达式或交互根载荷,compiler 按对应构造器生成入口代码。", + ("DeclKind", "definition"): "函数或类的完整声明,包含其名称、成员/参数及原文范围。", + ("StmtKind", "binding"): "let 新绑定,compiler 先分析/求右值,再登记目标初始化。", + ("StmtKind", "assignment"): "普通名称或成员赋值的完整节点,compiler 据构造器选择槽写入或属性写入。", + ("StmtKind", "statement"): "条件、循环或返回节点,compiler 按构造器处理路径、跳转或帧出口。", + ("StmtKind", "expression"): "语句位置的表达式;函数块丢弃结果,交互根可把它作为返回值。", + ("StmtKind", "declaration"): "交互根中的函数/类声明,compiler 预登记名称;嵌套声明当前被拒绝。", + ("ExprKind", "text"): "名称引用原文,由 compiler 名称分析解析到局部槽或全局名字表。", + ("ExprKind", "spelling"): "原始数字拼写,compiler 构造精确数值;不把文本先转为二进制浮点。", + ("ExprKind", "value"): "字符串解码后的拥有文本,原引号和转义仍可通过外层范围读取。", ("Name", "text"): "拥有名称文本,不依赖输入缓冲存活。", ("Program", "declarations"): "按源码顺序的声明;源码模块解析要求非空,文档结构本身不限制数量。", ("Decl", "kind"): "函数/类声明载荷,JSON 和类型采用 kind/data 标签。", @@ -565,7 +575,7 @@ def editor_types(definitions, schema_id): # 每个构造器按稳定顺序生成,不合并名称相近但身份不同的分支。 for variant in definition.variants: if definition.simple(): - # 拼接当前构造器的 kind 字面量和数据对象声明。 + # 无载荷简单枚举直接编码为字符串,不构造 kind/data 对象。 value = json.dumps(variant.name) # 当前分支不是无载荷简单枚举,需要构造带 kind 及可选 data 的判别对象。 else: -- 2.52.0 From 4f6d2eec7f64a4fe75301bac28fe7bea612aa7ef Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 19:51:02 +0800 Subject: [PATCH 58/62] docs(tests): correct ownership and reader failure explanations --- crates/frontend/tests/reader.rs | 20 ++++++++++---------- crates/objects/tests/attribute_storage.rs | 6 +++--- crates/objects/tests/store.rs | 10 +++++----- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/crates/frontend/tests/reader.rs b/crates/frontend/tests/reader.rs index 851a525..0e0e16c 100644 --- a/crates/frontend/tests/reader.rs +++ b/crates/frontend/tests/reader.rs @@ -60,7 +60,7 @@ fn strings_and_split_utf8_streams_produce_the_same_ast_and_exact_text() { // Reader 不剥除 BOM,词法器看到原始开头字符后才决定是否接受。 let bom = EndorReader::from_stream("", Cursor::new("\u{feff}1".as_bytes())).unwrap(); assert_eq!(bom.text(), "\u{feff}1"); - // 当前用例要求此阶段失败,随后检查具体类别、来源或底层原因,避免仅接受任意错误。 + // BOM 保留了三字节 UTF-8 编码,词法错误范围应恰好覆盖它,而不是 Reader 静默剥除。 let error = lex_source(&bom).unwrap_err(); assert_eq!(error.diagnostic.kind, DiagnosticKind::Lexical); assert_eq!(error.diagnostic.span.end, 3); @@ -71,7 +71,7 @@ fn strings_and_split_utf8_streams_produce_the_same_ast_and_exact_text() { fn source_diagnostics_keep_session_names_unicode_locations_and_original_offsets() { // 建立带来源名的原文快照,后续错误必须定位这份文本而非临时转换后的内容。 let source = EndorReader::from_text("", "\"日\" + )"); - // 当前用例要求此阶段失败,随后检查具体类别、来源或底层原因,避免仅接受任意错误。 + // 右括号没有对应表达式,语法失败仍需按含日文字符的完整原文计算列号和跨度。 let error = parse_source_expression(&source).unwrap_err(); assert_eq!(error.location.line, 1); assert_eq!(error.location.column, 7); @@ -87,7 +87,7 @@ fn source_diagnostics_keep_session_names_unicode_locations_and_original_offsets( // 建立带来源名的原文快照,后续错误必须定位这份文本而非临时转换后的内容。 let source = EndorReader::from_text("", "\r\n\"日\\q\""); - // 当前用例要求此阶段失败,随后检查具体类别、来源或底层原因,避免仅接受任意错误。 + // 第二行字符串含不支持的反斜杠转义,词法错误应保留 CRLF 后的行号和 Unicode 列。 let error = lex_source(&source).unwrap_err(); assert_eq!(error.diagnostic.kind, DiagnosticKind::Lexical); assert_eq!(error.location.line, 2); @@ -100,7 +100,7 @@ fn source_diagnostics_keep_session_names_unicode_locations_and_original_offsets( fn incomplete_or_invalid_utf8_is_rejected_without_replacement_characters() { // 覆盖无效起始字节、不完整 UTF-8 序列,以及一个合法 ASCII 前缀后的错误。 for bytes in [vec![0xff], vec![0xe6, 0x97], vec![b'a', 0xff]] { - // 当前用例要求此阶段失败,随后检查具体类别、来源或底层原因,避免仅接受任意错误。 + // 即使每次只读一个字节,聚合后非法 UTF-8 仍须作为读取编码错误拒绝,不能交给 lexer。 let error = EndorReader::from_stream("", OneByte(Cursor::new(bytes))).unwrap_err(); assert!(matches!(error.kind, SourceReadErrorKind::InvalidUtf8(_))); assert!(error.to_string().contains(": invalid UTF-8 at byte")); @@ -131,7 +131,7 @@ impl Read for FailingStream { #[test] /// 由 Rust 测试框架调用:读到一个有效字节后发生 IO 失败时仍整体失败,不向解析器提供半份快照。 fn a_failed_stream_never_returns_partially_read_source() { - // 当前用例要求此阶段失败,随后检查具体类别、来源或底层原因,避免仅接受任意错误。 + // 流先返回 x 后报错,Reader 必须整体传播 IO 失败,不能把已读前缀作为成功源码。 let error = EndorReader::from_stream("", FailingStream(false)).unwrap_err(); assert!(matches!(error.kind, SourceReadErrorKind::Io(_))); assert!(error.to_string().contains("stream failure")); @@ -182,13 +182,13 @@ fn file_sources_preserve_the_path_and_read_errors_are_not_lexical_errors() { assert!(parse_source(&source).is_ok()); // 单字节 0xff 不是 UTF-8,打开文件成功后应在解码阶段拒绝。 let invalid = SourceFile::new(&[0xff]); - // 当前用例要求此阶段失败,随后检查具体类别、来源或底层原因,避免仅接受任意错误。 + // 文件能打开但 0xff 无法解码,错误应带该文件路径并归为 InvalidUtf8。 let error = EndorReader::from_file(&invalid.0).unwrap_err(); assert!(matches!(error.kind, SourceReadErrorKind::InvalidUtf8(_))); assert!(error.to_string().contains(&invalid.0.display().to_string())); // 从基线路径换一个未创建的扩展名,验证文件不存在作为 IO 错误暴露。 let missing = file.0.with_extension("missing"); - // 当前用例要求此阶段失败,随后检查具体类别、来源或底层原因,避免仅接受任意错误。 + // 未创建的路径应返回 NotFound IO 原因,不能误分类为词法或编码失败。 let error = EndorReader::from_file(&missing).unwrap_err(); // 必须能解构出原始 IO 错误,之后检查 NotFound 或 PermissionDenied 的具体种类。 let SourceReadErrorKind::Io(error) = error.kind else { @@ -282,7 +282,7 @@ fn uri_snapshots_require_a_revision_and_preserve_it_in_source_diagnostics() { }; // 建立带来源名的原文快照,后续错误必须定位这份文本而非临时转换后的内容。 let source = EndorReader::from_endor(uri, &provider).unwrap(); - // 当前用例要求此阶段失败,随后检查具体类别、来源或底层原因,避免仅接受任意错误。 + // 已解析 URI 的字节是 UTF-8 但函数语法无效,解析诊断仍须显示 rev-one 来源。 let error = parse_source(&source).unwrap_err(); assert_eq!(error.origin, source.origin().clone()); assert!(error.to_string().contains("[revision rev-one]")); @@ -302,7 +302,7 @@ fn uri_snapshots_require_a_revision_and_preserve_it_in_source_diagnostics() { // 借用测试静态字节,load 时复制给 Reader 解码。 bytes: &[0xff], }; - // 当前用例要求此阶段失败,随后检查具体类别、来源或底层原因,避免仅接受任意错误。 + // 宿主已选择 rev-two 后才遇到非法 UTF-8,解码失败也必须保留该实际版本身份。 let error = EndorReader::from_endor(uri, &invalid).unwrap_err(); assert!(matches!(error.kind, SourceReadErrorKind::InvalidUtf8(_))); assert!(error.to_string().contains("[revision rev-two]")); @@ -344,7 +344,7 @@ fn invalid_uris_do_not_call_the_provider_and_denials_do_not_fall_back_to_files() )); } assert_eq!(provider.calls.get(), 0); - // 当前用例要求此阶段失败,随后检查具体类别、来源或底层原因,避免仅接受任意错误。 + // 基础 URI 合法但宿主拒绝访问,Reader 应保留 PermissionDenied,不尝试文件回退。 let error = EndorReader::from_endor("endor:anywhere", &DeniedProvider).unwrap_err(); // 必须能解构出原始 IO 错误,之后检查 NotFound 或 PermissionDenied 的具体种类。 let SourceReadErrorKind::Io(error) = error.kind else { diff --git a/crates/objects/tests/attribute_storage.rs b/crates/objects/tests/attribute_storage.rs index 0267331..6c7e83c 100644 --- a/crates/objects/tests/attribute_storage.rs +++ b/crates/objects/tests/attribute_storage.rs @@ -16,7 +16,7 @@ impl ObjectPayload for Proxy { fn kind_name(&self) -> &'static str { "proxy" } - /// Heap 分配/替换时记录一条拥有边,确保被代理存储或自引用 Cell 存活。 + /// Heap 分配/替换 Proxy 时记录后端存储边,释放门面或改指后端时归还该边。 fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { visit(self.0); } @@ -107,7 +107,7 @@ fn cyclic_and_foreign_attribute_storage_fail_without_mutating_fields() { // 异堆即使槽号相同也必须报 ForeignHandle,不当成普通缺字段。 let other = ObjectStore::new().unwrap(); assert_eq!(other.attribute_path(proxy), Err(VmError::ForeignHandle)); - // 归还后端外部根后仍由 Proxy 的载荷边维持存活。 + // Proxy 已改为自引用,不再持有 storage;归还其唯一外部根即可回收后端。 store.release(storage).unwrap(); store.release(proxy).unwrap(); // 外部根释放后只有 Proxy 自环残留,显式回收恰好一个对象。 @@ -130,7 +130,7 @@ impl ObjectPayload for ReflectiveType { fn allows_attributes(&self) -> bool { true } - /// Heap 分配/替换时记录一条拥有边,确保被代理存储或自引用 Cell 存活。 + /// Heap 分配反射载荷时记录 self_cell 强边,与稍后 Cell 反向持边构成测试环。 fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { visit(self.0); } diff --git a/crates/objects/tests/store.rs b/crates/objects/tests/store.rs index 0a14d1b..8d2daac 100644 --- a/crates/objects/tests/store.rs +++ b/crates/objects/tests/store.rs @@ -169,7 +169,7 @@ fn read_only_scopes_block_existing_container_and_type_writes_but_allow_fresh_res store.set_attr(fresh, "value", objects::Value::Nil).unwrap(); // Record 不原地赋值,只读引用重绑定可以返回 false 而不修改旧对象。 assert!(!store.assign_from(record, fresh).unwrap()); - // 嵌套层快照/区间只影响自身,结束必须按 LIFO。 + // 内层重新快照所有存活对象,fresh 此时也受保护;结束必须按 LIFO。 let nested = store.begin_read_only(); assert!( store @@ -206,12 +206,12 @@ fn read_tracking_regions_repeat_shared_reads_and_preserve_enclosing_dependencies // 第二对象仅在内层首次读取,外层集合也必须包含它。 let second = store.new_object().unwrap(); assert!(store.begin_read_tracking().is_err()); - // 进入时快照已有对象,之后分配的 fresh 不在本层保护集合。 + // 进入时把 first、second 列为已有对象,后面的读取才会成为本层依赖。 let scope = store.begin_read_only(); // 外层区间开始后,其内所有读取都要包含在最终结果中。 let outer = store.begin_read_tracking().unwrap(); store.object(first).unwrap(); - // 嵌套层快照/区间只影响自身,结束必须按 LIFO。 + // 子区间嵌套不另建对象保护快照;其读取同时传播给 outer,结束仍须按 LIFO。 let nested = store.begin_read_tracking().unwrap(); store.object(first).unwrap(); store.object(second).unwrap(); @@ -232,8 +232,8 @@ fn read_tracking_regions_repeat_shared_reads_and_preserve_enclosing_dependencies // 整个调用结束后未完成区间已丢弃,不能再拿旧令牌结束。 assert!(store.end_read_tracking(1).is_err()); assert!(!store.is_read_only()); - // 移除一函数仅减一条代码边,另一函数仍保留代码和常量。 + // first 没有内部拥有边,归还唯一外部根后可立即回收;借用读取记录不会保活它。 store.release(first).unwrap(); - // 最后一个函数释放后,代码/全局/常量无根链一起回收。 + // second 同样独立归还唯一根;本场景没有函数、代码对象或对象互引环。 store.release(second).unwrap(); } -- 2.52.0 From 66f9e0764c15c50f8ba1a9f8dad9665a7d38a4dd Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 19:54:49 +0800 Subject: [PATCH 59/62] docs(tests): clarify compiler scenarios and capability helper ownership --- crates/compiler/tests/classes.rs | 4 ++++ crates/compiler/tests/pipeline.rs | 6 +++--- crates/core/tests/builtins.rs | 11 ++++++----- crates/core/tests/typed_traits.rs | 15 ++++++++++----- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/crates/compiler/tests/classes.rs b/crates/compiler/tests/classes.rs index 3a74e99..c0a111a 100644 --- a/crates/compiler/tests/classes.rs +++ b/crates/compiler/tests/classes.rs @@ -325,6 +325,7 @@ fn declared_field_checks_cover_construction_inheritance_writes_and_gc() { .unwrap(); vm.release(roots.object).unwrap(); vm.release(roots.type_type).unwrap(); + // 一次模块声明建立继承字段、名义对象字段及未知注解三种约束,真正的构造/写入在后面分别触发。 execute( &mut vm, globals, @@ -336,6 +337,7 @@ class Unknown { value: Missing } "#, ParseMode::Module, ); + // 每份交互源码只提交一条语句:先登记 Child 实例,再让 Holder 的字段持有同一实例。 // Interactive intentionally accepts one complete statement per source unit. execute( &mut vm, @@ -381,6 +383,7 @@ class Unknown { value: Missing } 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. @@ -446,6 +449,7 @@ class Seed: Link {} 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, diff --git a/crates/compiler/tests/pipeline.rs b/crates/compiler/tests/pipeline.rs index e46c151..54dd23e 100644 --- a/crates/compiler/tests/pipeline.rs +++ b/crates/compiler/tests/pipeline.rs @@ -33,7 +33,7 @@ fn module(vm: &mut Vm, compiled: &CompiledCode) -> (ObjectRef, ObjectRef) { /// 测试按名称从环境借用函数句柄;不 retain,调用者须保持 globals 或其他拥有图存活。 fn function(vm: &Vm, globals: ObjectRef, name: &str) -> ObjectRef { - // 环境键命中必须是函数对象;这里只借用,不新取得外部根。 + // 环境键必须命中对象句柄;此辅助函数不再下转型证明函数类别,实际可调用性由后续 VM 调用验证。 let Some(Value::Object(function)) = vm.dict_get(globals, &Value::Text(name.into())).unwrap() else { panic!("missing function {name}") @@ -357,7 +357,7 @@ fn argument_and_recursion_failures_unwind_vm_frames_and_keep_traceback() { } #[test] -/// 由 Rust 测试框架调用:编译选项只声明合法名字,不注入 VM 值;空环境执行仍报告 NameNotFound。 +/// 由 Rust 测试框架调用:编译选项只登记可见名字,不注入 VM 值;空环境执行仍报告 NameNotFound。 fn declared_host_names_are_checked_again_at_runtime() { // 当前用例的源码或带来源快照,下面按指定模式解析/编译。 let source = EndorReader::from_text("", "missing"); @@ -669,7 +669,7 @@ fn source_control_flow_and_exact_arithmetic_execute_real_bytecode() { fn control_flow_definite_initialization_large_jumps_and_loop_budgets() { // 拼接较长 if/while 体,迫使条件出口和回边跨越扩展编码距离。 let mut body = String::from("function main(): Number { i=0 while i<2 { if i==0 {"); - // 重复运行同一函数三次,每次归还结果,以对象数验证代码复用。 + // 追加三百条不改变 i 的赋值,仅增大跳转跨越的代码宽度;真正的循环推进在后面单独追加。 for _ in 0..300 { body.push_str("i=i+0 "); } diff --git a/crates/core/tests/builtins.rs b/crates/core/tests/builtins.rs index a942d6e..90f30c1 100644 --- a/crates/core/tests/builtins.rs +++ b/crates/core/tests/builtins.rs @@ -1,5 +1,6 @@ //! 测试框架调用的内置值/载荷集成回归,通过公开 Vm API 验证 objects 与执行器协议一致。 //! 各场景区分 Rust 表示相等、语言数值/内容比较、外部拥有根和容器内部强边。 +//! 部分场景只核对局部协议并以 VM 析构收尾;显式释放/环回收场景另检查句柄失效、计数或空堆。 use endor_core::{ CodeLayout, CompareOp, EndorBaseExceptionObject, EndorCode, EndorDictObject, @@ -52,7 +53,7 @@ fn numbers_preserve_large_digits_decimal_scale_exponents_and_fraction_parts() { } #[test] -/// 验证 IEEE 位保真、零真值、复数显示及未实现数值 hash/比较的明确拒绝。 +/// 验证 IEEE 位保真、零真值、复数显示,以及数值 hash/复数比较的明确拒绝;实数数学比较另有实现。 fn number_objects_preserve_ieee_bits_and_reject_undefined_numeric_operations() { // 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。 let mut vm = Vm::new().unwrap(); @@ -258,7 +259,7 @@ fn container_cycles_and_duplicate_edges_are_collected() { fn functions_retain_globals_constants_defaults_and_shared_closure_cells() { // 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。 let mut vm = Vm::new().unwrap(); - // 准备函数环境;本场景可故意使用非字典以测试种类校验。 + // 合法字典环境由两函数共同持有,稍后再反向保存第一个函数形成待回收的环境环。 let globals = vm.new_dict().unwrap(); // 常量初始有测试根,代码常量表和默认值还会独立拥有强边。 let constant = vm.new_string("constant").unwrap(); @@ -300,7 +301,7 @@ fn functions_retain_globals_constants_defaults_and_shared_closure_cells() { vm.release(code).unwrap(); // 常量原测试根归还,其余代码/默认值边仍保持存活。 vm.release(constant).unwrap(); - // 归还 Cell 原根,闭包或字典边仍可持有它,最后无根环交给 GC。 + // 两函数都已保存同一捕获 Cell,归还测试根后更新仍通过这两条内部边保持合法。 vm.release(cell).unwrap(); vm.cell_set(cell, text("updated")).unwrap(); // 逐个读取两个函数捕获的同一 Cell,必须观察相同更新。 @@ -325,7 +326,7 @@ fn functions_retain_globals_constants_defaults_and_shared_closure_cells() { fn malformed_function_environments_are_rejected_without_retaining_any_edges() { // 每个测试建立独立 VM;句柄所有权由 API 决定,不依赖复制值自动 retain。 let mut vm = Vm::new().unwrap(); - // 准备函数环境;本场景可故意使用非字典以测试种类校验。 + // 故意以普通 Record 冒充全局字典,第一次函数构造应在保留边之前拒绝。 let globals = vm.new_object().unwrap(); // 最小装载常量并返回的逻辑指令,用于只改变环境或闭包布局的错误测试。 let instructions = [ @@ -356,7 +357,7 @@ fn malformed_function_environments_are_rejected_without_retaining_any_edges() { .unwrap(); // 托管代码对象有一份根,函数载荷还会额外持有代码边。 let code = vm.new_code(code).unwrap(); - // 准备函数环境;本场景可故意使用非字典以测试种类校验。 + // 第二次改用合法字典环境,只保留“代码需要一个自由 Cell、实传零个”的错误。 let globals = vm.new_dict().unwrap(); assert!( vm.new_function("bad", code, globals, vec![], vec![]) diff --git a/crates/core/tests/typed_traits.rs b/crates/core/tests/typed_traits.rs index 2245181..3b53fbf 100644 --- a/crates/core/tests/typed_traits.rs +++ b/crates/core/tests/typed_traits.rs @@ -27,11 +27,15 @@ fn capability( /// 测试辅助:构造先写 globals.ran=42 再返回给定常量的源码方法,附 owner 与类型注解。 fn source( vm: &mut Vm, - /// 含 Current 注解名称的字典,包装载荷通过 captures 持边。 + // 借用源码函数的全局环境,new_function 为它建立内部边;体执行时向其中写 ran。 globals: ObjectRef, + // 附入 SourceSignature 的声明类名,运行时结合实际接收者解析 owner 类型。 owner: &str, + // 含显式 self 的参数注解列表,长度同时决定参数和局部槽数量。 types: &[&str], + // 返回注解文本,调用/契约检查时解析,不在本辅助函数绑定为类型句柄。 returns: &str, + // 体写 ran 后返回的常量;对象变体在代码分配后由常量表内部边持有。 value: Value, ) -> ObjectRef { // 手工代码提供可观察 ran 副作用,参数/局部布局按类型列表一一对应。 @@ -59,7 +63,7 @@ fn source( .unwrap(); // 分配代码后常量成为内部强边,函数将持有代码引用。 let code = vm.new_code(code).unwrap(); - // 分配源码/原生函数拥有根,完整签名与显式 captures 各自报告托管引用。 + // 分配源码函数根,载荷分别持有代码和 globals;这里没有默认值或自由 Cell,也不使用原生 captures。 let function = vm .new_function("check", code, globals, vec![], vec![]) .unwrap(); @@ -75,7 +79,7 @@ impl NativeCallable for Native { fn signature(&self) -> Option { Some(self.0.clone()) } - /// 原生测试入口;Native 返回直接整数,RebindThenReturn 先重绑定环境再返回借用对象。 + /// VM 原生分派调用,忽略已由签名检查的实参,只返回直接整数,不访问环境或分配对象。 fn invoke(&self, _: &mut NativeContext<'_>, _: &[Value]) -> Result { Ok(NativeResult::Return(Value::Number(7.into()))) } @@ -293,6 +297,7 @@ fn typed_trait_definitions_trace_their_signature_types_without_hidden_roots() { } #[derive(Debug)] +/// 最后一项回归的宿主实现:包装器显式 captures 保活这些句柄,体内重绑定会使外层返回能力失效。 struct RebindThenReturn { /// 自身直接返回约束仍满足 Model,但外层能力返回约束需更严格重验 Feature。 signature: CallSignature, @@ -308,7 +313,7 @@ impl NativeCallable for RebindThenReturn { fn signature(&self) -> Option { Some(self.signature.clone()) } - /// 原生测试入口;Native 返回直接整数,RebindThenReturn 先重绑定环境再返回借用对象。 + /// VM 原生分派调用,先通过上下文重绑定 Current,再借用返回 Model 实例以触发外层能力重验。 fn invoke(&self, cx: &mut NativeContext<'_>, _: &[Value]) -> Result { // 函数体把 Current 重绑定为无关 Other,主动使返回 Model 的 Feature 契约失效。 cx.dict_set( @@ -342,7 +347,7 @@ fn actual_trait_returns_revalidate_capabilities_after_body_side_effects_and_rest let model = vm .new_type("Model", roots.object, TypeConstructor::Record) .unwrap(); - // 源码方法注解在此环境解析,同时 ran 标志观测函数体是否进入。 + // 源码方法的 Current 注解在此字典动态解析;本场景只检查该方法契约,不执行其写 ran 的体。 let globals = vm.new_dict().unwrap(); vm.dict_set( globals, -- 2.52.0 From e44bcdce3e8b1836f211aef205e9509de1b799d6 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 19:55:50 +0800 Subject: [PATCH 60/62] docs(tests): explain runtime payload fixtures and allocation stress --- crates/core/tests/runtime.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/core/tests/runtime.rs b/crates/core/tests/runtime.rs index 4ba7ebc..876f30b 100644 --- a/crates/core/tests/runtime.rs +++ b/crates/core/tests/runtime.rs @@ -9,6 +9,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use endor_core::{CompareOp, NativeFunction, ObjectPayload, ObjectRef, Record, Value, Vm, VmError}; #[derive(Debug)] +/// 生命周期场景的宿主资源:以 Drop 计数观测真正销毁,edges 同时让载荷参与托管图。 struct Resource { /// VM 外观察实际析构次数的共享计数器。 drops: Arc, @@ -49,6 +50,7 @@ impl ObjectPayload for Resource { } #[derive(Debug)] +/// 协议覆盖场景的自定义整数载荷,与内置 EndorNumberObject 分开,专门验证 Store 的动态分派。 struct Number(i64); impl ObjectPayload for Number { @@ -72,7 +74,7 @@ impl ObjectPayload for Number { Ok(self.0 != 0) } - /// 载荷自定义哈希协议;Number 按值,ReflectedOrdering 明确拒绝。 + /// Store::hash 调用此载荷协议,按整数值生成哈希;负数按 Rust as 转为 u64。 fn hash(&self, _identity: ObjectRef) -> Result { Ok(self.0 as u64) } @@ -299,11 +301,11 @@ fn released_handles_cannot_alias_new_objects_or_new_vms() { fn boxed_objects_keep_their_addresses_when_the_slot_vector_grows() { // 建立独立堆,句柄不能跨测试/VM 使用。 let mut vm = Vm::new().unwrap(); - // 第一个独立对象/资源保留一份根,与 second/equal 比较身份或引用关系。 + // 保持首个对象存活,扩容前后比较其独立 Box 地址而非槽向量中的位置。 let first = vm.new_object().unwrap(); // 保存对象实体地址,不保存可能因向量扩容变化的槽地址。 let address = std::ptr::from_ref(vm.object(first).unwrap()); - // 压力设置:足够多次分配或链节点,检验稳定盒与非递归清理。 + // 额外分配四千零九十六个对象,迫使 slots 向量扩容;所有对象在 VM 结束时清理。 for _ in 0..4096 { vm.new_object().unwrap(); } @@ -461,6 +463,7 @@ fn rust_payload_operations_override_defaults_without_language_types() { } #[derive(Debug)] +/// 反向比较场景的右操作数:只对 Gt 返回 true,用于识别左 Lt 是否正确交换方向。 struct ReflectedOrdering; impl ObjectPayload for ReflectedOrdering { @@ -483,7 +486,7 @@ impl ObjectPayload for ReflectedOrdering { Ok(Some(operation == CompareOp::Gt)) } - /// 载荷自定义哈希协议;Number 按值,ReflectedOrdering 明确拒绝。 + /// Store::hash 调用时明确返回不支持,不能退回默认对象身份哈希。 fn hash(&self, _identity: ObjectRef) -> Result { Err(VmError::UnsupportedOperation("hash")) } @@ -562,6 +565,7 @@ fn native_calls_dispatch_validate_arguments_and_propagate_errors() { } #[derive(Debug)] +/// 非法结果场景的故障注入载荷:只保存未 trace 的借用身份,调用时故意返还它让 VM 拒绝。 struct ReturnsObject(ObjectRef); impl ObjectPayload for ReturnsObject { @@ -595,7 +599,7 @@ fn native_results_cannot_smuggle_foreign_or_dead_handles_into_the_vm() { assert_eq!(vm.call(invalid_return, &[]), Err(VmError::ForeignHandle)); // 本堆对象释放后制造过期返回值,不与异堆错误混淆。 let dead = vm.new_object().unwrap(); - // 之后只剩失效句柄文本,回调返回不能把它重新保活。 + // 之后只剩失效的借用身份,回调返回不能把它重新保活。 vm.release(dead).unwrap(); // 故意不 trace 持有的句柄,因为这里测试的是非法借用结果而非合法拥有边。 let invalid_return = vm.allocate(ReturnsObject(dead)).unwrap(); -- 2.52.0 From 057228a0fd6b36a905a9e6d6efa15a554f5b3a89 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 20:00:13 +0800 Subject: [PATCH 61/62] docs(tests): correct nominal and native contract scenario boundaries --- crates/core/tests/method_contracts.rs | 14 ++++++++++---- crates/core/tests/native_context.rs | 16 ++++++++++------ crates/core/tests/types.rs | 8 ++++---- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/crates/core/tests/method_contracts.rs b/crates/core/tests/method_contracts.rs index d355421..679f2b5 100644 --- a/crates/core/tests/method_contracts.rs +++ b/crates/core/tests/method_contracts.rs @@ -10,10 +10,15 @@ use std::collections::BTreeMap; /// 测试辅助,生成先写 globals.ran=42 再返回 result 的源码方法,附显式 owner/参数/返回注解。 fn source( vm: &mut Vm, + // 借用全局字典,源码函数持环境边;副作用 ran 与动态注解名称都从这里观察。 globals: ObjectRef, + // 写入源码签名的方法所属类名,按接收者继承链解析该声明身份。 owner: &str, + // 含显式 self 的参数注解文本,也决定位置参数及局部槽数量。 parameters: &[&str], + // 返回注解文本,实际类型在注册/调用契约检查时解析。 returns: &str, + // 写 ran 后返回的常量;对象身份由托管代码常量边持有,不因 Value 移动自动 retain。 result: Value, ) -> ObjectRef { // 手工指令保证源码体可观测,参数/局部槽按注解数量保持一致。 @@ -50,6 +55,7 @@ fn source( function } /// 测试辅助,以给定基类构造只声明 check 方法、无自有字段的源码类。 +/// base/function 均借用,成功返回新类拥有根并建立内部边;失败不消耗调用方持有的根。 fn class( vm: &mut Vm, name: &str, @@ -300,7 +306,7 @@ fn global_rebinding_is_rechecked_before_the_source_body_and_recovers() { "Actual", Value::Object(result), ); - // 先创建当前合法后代,再对父方法提出可能使它失效的更新。 + // 先创建当前合法后代;下面只重绑定其 Actual 注解名,不修改父方法或子方法载荷。 let child = class(&mut vm, "Child", base, child_fn).unwrap(); // 显式 self 为 Child 实例,调用时据其继承链解析各方法 own-class 注解。 let receiver = vm.new_object().unwrap(); @@ -487,7 +493,7 @@ fn unknown_forward_names_defer_until_call_without_entering_the_body() { ); // 定义阶段允许未知名称的派生创建,实例调用时会重新核对。 let child = class(&mut vm, "Child", base, child_fn).unwrap(); - // 实例提供实际接收者身份,绑定效果分析可据此查 helper,未绑定函数则未知。 + // 显式 self 使用 Child 实例,让调用预检按其继承链解析 Base/Child,再检查仍未知的 Later。 let receiver = vm.new_object().unwrap(); vm.attach_type(receiver, child).unwrap(); assert!(matches!( @@ -577,7 +583,7 @@ fn verified_source_read_only_effects_constrain_native_overrides() { // 后代继承已证明只读的方法,候选原生覆盖不能扩大已知效果。 let child = vm.new_type("Child", base, TypeConstructor::Record).unwrap(); assert_eq!(vm.call_effect(source).unwrap(), Some(CallEffect::ReadOnly)); - // 实例提供实际接收者身份,绑定效果分析可据此查 helper,未绑定函数则未知。 + // 以 Child 实例创建绑定方法,核对绑定形式仍可证明只读;此处未绑定源码本身也已由白名单证明。 let receiver = vm.new_object().unwrap(); vm.attach_type(receiver, child).unwrap(); // 绑定对象拥有函数和实际接收者边,分析可用 receiver 区分实例方法/字段。 @@ -594,7 +600,7 @@ fn verified_source_read_only_effects_constrain_native_overrides() { CallEffect::ReadOnly, CallEffect::Delegated, ] { - // 函数持有其代码/全局边,效果查询从当前载荷及环境读取快照。 + // 候选为原生包装器,仅签名引用类型形成 trace 边;效果查询读取其声明,不存在源码代码/全局字段。 let function = vm .allocate(ContextNativeFunction::new( "check", diff --git a/crates/core/tests/native_context.rs b/crates/core/tests/native_context.rs index 8f74b97..06be2d5 100644 --- a/crates/core/tests/native_context.rs +++ b/crates/core/tests/native_context.rs @@ -62,7 +62,7 @@ fn scoped_allocations_return_one_root_and_clean_up_errors() { /// 返回目标借用身份,由包装载荷 captures 保活,NativeContext 为 Invoke 输出取得拥有根。 struct Delegate(ObjectRef); impl NativeCallable for Delegate { - /// VM 在 NativeContext 内调用此测试分派器,临时分配与输入由上下文保活,返回/委托后移交输出根。 + /// VM 分派时原样委托给 captures 保活的目标;参数 clone 仍借用,NativeContext 随后为 Invoke 输出逐项取根。 fn invoke(&self, _cx: &mut NativeContext<'_>, args: &[Value]) -> Result { Ok(NativeResult::Invoke { function: self.0, @@ -157,7 +157,7 @@ fn native_delegation_shares_budget_and_cleans_owned_arguments() { assert_eq!(vm.ref_count(target).unwrap(), 2); // VM 调用已完成根移交,返回对象应只有一份调用者根。 let value = vm.call(delegate, &[]).unwrap(); - // 归还成功结果,之后只剩函数载荷,临时对象应已自动清理。 + // 归还 Allocate 返回的独立结果根;delegate、target、arg 的测试根仍在下面分别释放。 vm.release_value(value).unwrap(); vm.release(delegate).unwrap(); vm.release(target).unwrap(); @@ -237,6 +237,7 @@ fn notification_overflow_requires_rescan_instead_of_losing_changes_silently() { } #[derive(Debug)] +/// 只读场景的复制器:读输入字段并构造新结果,可选故意改旧输入以验证写屏障及错误退出。 struct ReadResult { /// 输入借用身份,包装载荷 captures 负责保活。 source: ObjectRef, @@ -381,6 +382,7 @@ fn annotation_reads_remain_dependencies_without_becoming_program_content_reads() } #[derive(Debug)] +/// 完整签名场景共用分派器,以计数器区分预检/回调/返回检查,支持委托、新分配或回传实参。 struct TypedNative { /// 构造时快照的完整签名,包装载荷负责 trace 类型定义。 signature: endor_core::CallSignature, @@ -479,7 +481,7 @@ fn delegated_source_results_obey_every_native_contract_without_extra_frames() { let code = vm.new_code(code).unwrap(); // 源码环境有分配根,函数持有它的内部边。 let globals = vm.new_dict().unwrap(); - // 源码函数返回文本或 Nil,作为后续返回契约一致/不一致的终点。 + // 此源码函数固定返回文本;inner 的 String 返回约束可通过,outer 的 Number 约束必须失败。 let source = vm .new_function("source", code, globals, vec![], vec![]) .unwrap(); @@ -705,10 +707,11 @@ fn legacy_native_signatures_check_optional_variadic_types_and_metadata() { } #[derive(Debug)] +/// 移除自身载荷的回归分派器,迫使签名类型在原持边消失后仍由执行检查根保活。 struct EraseThenDelegate { /// 构造时快照的完整签名,包装载荷负责 trace 类型定义。 signature: endor_core::CallSignature, - /// 被委托源码函数,由包装载荷 captures 保活至回调中输出保护接管。 + /// 被委托源码函数最初有 captures 边;载荷自替换后该边消失,本测试另保留 source 根保护目标。 target: ObjectRef, } impl NativeCallable for EraseThenDelegate { @@ -719,6 +722,7 @@ impl NativeCallable for EraseThenDelegate { /// 测试原生分派入口,回调内借用参数/目标,NativeContext 校验并移交 Return 或 Invoke 输出根。 fn invoke(&self, cx: &mut NativeContext<'_>, _: &[Value]) -> Result { // 将当前函数换为普通 Record,移除签名及捕获边,迫使返回约束靠执行器自己的根存活。 + // self.target 的存活此时依赖测试未归还的 source 根,Arc 中的 ObjectRef 不构成托管边。 cx.replace_payload(cx.callee(), endor_core::Record)?; Ok(NativeResult::Invoke { function: self.target, @@ -758,7 +762,7 @@ fn delegated_return_type_survives_removal_of_the_original_signature_edges() { let code = vm.new_code(code).unwrap(); // 源码环境有分配根,函数持有它的内部边。 let globals = vm.new_dict().unwrap(); - // 源码函数返回文本或 Nil,作为后续返回契约一致/不一致的终点。 + // 此源码函数固定返回 Nil,故意不满足包装器的 Transient | Number 返回联合。 let source = vm .new_function("source", code, globals, vec![], vec![]) .unwrap(); @@ -916,7 +920,7 @@ fn native_union_and_nullable_contracts_accept_each_branch_and_reject_invalid_met } #[test] -/// Delegated 包装器能进入只读域,但实际目标仍在同域核对效果,目标换为纯函数后可通过。 +/// Delegated 包装器能进入只读域,但实际目标仍在同域核对效果,目标改为 ReadOnly 声明后可通过。 fn delegated_effects_keep_the_original_read_only_scope_and_check_the_actual_target() { use endor_core::{AnnotationType as T, CallEffect, CallSignature}; use std::sync::{ diff --git a/crates/core/tests/types.rs b/crates/core/tests/types.rs index 03ce664..265bdea 100644 --- a/crates/core/tests/types.rs +++ b/crates/core/tests/types.rs @@ -242,7 +242,7 @@ fn late_trait_registration_and_ancestor_updates_validate_existing_descendants() vm.object_store().lookup_method(base, "render").unwrap(), Some(good) ); - // 第三堆查询已有类型/能力应报 ForeignHandle,不是简单 false。 + // 另建独立 VM 查询原堆类型/能力应报 ForeignHandle,不是简单 false。 let foreign = Vm::new().unwrap(); assert_eq!( foreign.implements_trait(base, capability), @@ -357,7 +357,7 @@ fn failed_nominal_return_releases_value_and_captured_type_roots() { Value::Object(expected), ) .unwrap(); - // 测试用已验证指令,返回常量 Nil;数量由参数布局决定。 + // 零参数代码返回 Other 实例常量,故意违背下面声明的 Expected 返回类型。 let code = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadConst, 0), @@ -438,7 +438,7 @@ fn native_arity_checks_precede_effects_and_traits_require_published_signatures() // 副作用标志载体被 CountedNative 的显式 captures 边保活。 let state = vm.new_object().unwrap(); vm.set_attr(state, "called", Value::Bool(false)).unwrap(); - // 原生载荷也按 is_function 分类附着类型,数量协议仍独立。 + // 本场景未配置 Function 名义类型;包装器发布的数量区间独立用于预检及能力匹配。 let native = vm .allocate(ContextNativeFunction::new( "counted", @@ -477,7 +477,7 @@ fn native_arity_checks_precede_effects_and_traits_require_published_signatures() let Value::Object(instance) = vm.call(class, &[]).unwrap() else { panic!("instance") }; - // 绑定结果持有函数与 receiver,且本身应自动取得 Function 类型。 + // 绑定结果持有 native 和 instance;这里只核对扣除 self 后的数量区间,不要求 Function 名义类型。 let Value::Object(bound) = vm.bind_attr(instance, "run").unwrap() else { panic!("bound") }; -- 2.52.0 From ab0be1efd3495a2b188f60ef2542492696e39add Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 20:03:17 +0800 Subject: [PATCH 62/62] docs(tests): correct execution root and closure result explanations --- crates/core/tests/execution.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/crates/core/tests/execution.rs b/crates/core/tests/execution.rs index 4c3567b..ffb1205 100644 --- a/crates/core/tests/execution.rs +++ b/crates/core/tests/execution.rs @@ -1,5 +1,6 @@ //! 通过手工指令验证 VM 求值、根生命周期及暂停行为,测试框架直接调用,无需源码解析器。 //! op/simple/make_code 统一构造已验证代码;错误编码用 EndorCode 直接检验构造阶段拒绝。 +//! 部分协议场景只核对局部结果/计数,保留的测试根由 VM 析构收尾;显式清理场景另断言失效或空堆。 use std::any::Any; use std::sync::Arc; @@ -26,8 +27,11 @@ fn text(value: &str) -> Value { /// 测试辅助,编码并验证指令/常量/布局后分配代码拥有根;失败直接使测试失败。 fn make_code( vm: &mut Vm, + // 逻辑指令借用输入;编码器处理扩展前缀,代码构造器检查索引、跳转和栈高。 instructions: &[Instruction], + // 常量表示移入代码,含对象时只有成功托管分配才建立内部强边;不消耗调用方对象根。 constants: Vec, + // 参数/局部/Cell/名称布局随代码移入,执行帧按它分配和寻址。 layout: CodeLayout, ) -> ObjectRef { vm.new_code(EndorCode::from_instructions(instructions, constants, layout).unwrap()) @@ -151,7 +155,7 @@ fn opt_in_assignment_keeps_alias_identity_ordinary_values_and_root_accounting() fn failed_opt_in_assignment_preserves_target_and_cleans_execution_roots() { // 独立 VM 隔离对象计数/全局绑定,清理断言只针对本场景。 let mut vm = Vm::new().unwrap(); - // 共享目标以 1 初始化,同时另由 alias 持有;赋值后身份应保持。 + // 共享目标以 1 初始化,globals.value 稍后持有它;拒绝新值后身份及原内容都应保持。 let old = vm.allocate(SharedReference(AtomicUsize::new(1))).unwrap(); // 以哨兵值 99 触发校验拒绝,不允许污染旧目标值。 let rejected = vm.allocate(SharedReference(AtomicUsize::new(99))).unwrap(); @@ -505,7 +509,7 @@ fn argument_count_and_foreign_argument_failures_do_not_retain_inputs() { let mut other = Vm::new().unwrap(); // 异堆参数在执行源码之前拒绝,不改变当前函数计数。 let foreign = other.new_object().unwrap(); - // 保存调用前计数,结果交付多一根,归还结果后必须恢复。 + // 保存函数调用前计数;异堆实参预检失败后不应留下临时函数执行根。 let before = vm.ref_count(function).unwrap(); assert_eq!( vm.call(function, &[Value::Object(foreign)]), @@ -573,7 +577,7 @@ fn native_return_of_a_temporary_argument_is_retained_before_inputs_are_popped() let mut vm = Vm::new().unwrap(); // 函数执行环境的拥有根,函数也经载荷强边持有该字典。 let globals = vm.new_dict().unwrap(); - // 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。 + // 分配旧式原生 echo 根,本载荷没有源码代码/全局字段;globals 随后为它建立值边。 let function = vm .allocate(EndorNativeFunctionObject::new("echo", echo)) .unwrap(); @@ -599,7 +603,7 @@ fn native_return_of_a_temporary_argument_is_retained_before_inputs_are_popped() ); // 执行结果拥有根可跨越代码与全局生命周期。 let result = vm.execute(code, globals).unwrap(); - // 移除代码常量边后,返回图必须自己保留所需对象;不是只靠旧帧保活。 + // 包装代码的常量表为空;释放代码后,临时字典必须仅凭独立调用结果根继续存活。 vm.release(code).unwrap(); // 归还环境测试根,临时执行函数已清理,结果不能依赖其持续存在。 vm.release(globals).unwrap(); @@ -620,7 +624,7 @@ fn native_failure_unwinds_temporary_roots_and_keeps_execution_locations() { let mut vm = Vm::new().unwrap(); // 函数执行环境的拥有根,函数也经载荷强边持有该字典。 let globals = vm.new_dict().unwrap(); - // 创建源码/原生函数拥有根,代码/全局/默认值内部边由分配协议另计。 + // 分配恒定报错的旧式原生回调根,环境另持函数边;后面的源码包装器负责 Call 与回溯位置。 let function = vm .allocate(EndorNativeFunctionObject::new("fail", fail)) .unwrap(); @@ -703,9 +707,9 @@ fn recursion_uses_explicit_frames_and_unwinds_at_the_frame_limit() { assert_eq!(vm.ref_count(function).unwrap(), roots); // 调用前/后按场景归还测试函数根;全局字典可能仍持有函数内部边。 vm.release(function).unwrap(); - // 移除代码常量边后,返回图必须自己保留所需对象;不是只靠旧帧保活。 + // 归还代码测试根;递归函数仍持代码边,需随函数/环境无外根环一起回收。 vm.release(code).unwrap(); - // 归还环境测试根,临时执行函数已清理,结果不能依赖其持续存在。 + // 归还环境测试根后只剩 globals→function→globals 及代码边,显式 GC 应回收三个对象。 vm.release(globals).unwrap(); assert_eq!(vm.collect_cycles(), 3); } @@ -874,7 +878,7 @@ fn captured_argument_cells_keep_their_contents_after_the_frame_returns() { .unwrap(); // 参数对象有测试根,帧初始化提升 Cell 时另建立内容强边。 let value = vm.new_string("captured").unwrap(); - // 函数把已有参数/默认值作为结果,身份不变但结果仍取得独立拥有根。 + // LoadClosure 返回新建的参数 Cell,并非原参数字符串;结果根保活 Cell,Cell 内部边再保活参数。 let returned = vm.call(function, &[Value::Object(value)]).unwrap(); // 归还原参数测试根,返回的 Cell 内容边仍须保活它。 vm.release(value).unwrap(); @@ -2010,7 +2014,7 @@ impl ObjectPayload for AwaitGate { fn kind_name(&self) -> &'static str { "await-gate" } - /// Heap 分配、回收时遍历内部对象边;立即值没有托管引用,无需报告。 + /// Heap 分配载荷时据此登记对象边快照,回收沿已登记的边处理;立即值无需报告。 fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { // 只有对象值携带堆引用;必须报告这一边,使测试释放原根后结果仍存活。 if let Value::Object(reference) = self.value { -- 2.52.0