//! 给源协议返回的对象列表提供受控封装。列表内容通过托管引用被追踪,读方法只暴露检查后的条目。 //! 用于子节点组成、布局结果和几何输入等需要保留对象身份的场景;不是窗口线程共享的可变容器。 //! //! 调用路径:桥接 bootstrap 注册 Constructor;Mount/视觉协议/绘图入口调用 collection 生成返回列表。 //! source-layout/graphics/三维等消费者用 items 借用成员;源代码通过 at/append/remove/extend/added 操作。 //! 列表只允许对象成员;子类普通字段另按名义类型检查,不能把标量字段值混进成员前缀。 //! //! Managed indexed host results; values and the at method are traced edges. use endor_core::{ ContextNativeFunction, NativeCallable, NativeContext, NativeResult, ObjectPayload, ObjectRef, Value, VmError, }; #[derive(Debug)] /// 托管对象序列,trace 负责拥有其中的对象引用;源代码通过受检查方法读取。 struct ObjectList(Vec); impl ObjectPayload for ObjectList { /// 声明此 payload 的赋值遵循引用身份语义,避免把内部宿主状态当作可复制值。 fn reference_assignment_only(&self) -> bool { true } /// 提供受检查的 Any 下转型入口,让宿主按具体 payload 类型读取数据。 fn as_any(&self) -> &dyn std::any::Any { self } /// 返回运行时诊断/类型检查使用的稳定 payload 名称。 fn kind_name(&self) -> &'static str { "tessera-object-list" } /// 声明此 payload 是否允许使用托管属性存储,具体字段仍由类型与调用检查约束。 fn allows_attributes(&self) -> bool { true } /// 枚举本对象真正拥有的托管强引用,供 Aster 引用追踪和循环回收使用。 fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { // Aster 引用追踪逐条遍历成员拥有边;变量名 mount 沿用最初用途,但列表也可持有其他对象。 for mount in &self.0 { visit(*mount); } } } #[derive(Debug)] /// 绑定到某个列表对象的下标访问适配器。 struct At(ObjectRef); impl NativeCallable for At { /// 给出参数与返回值的托管类型契约;实际操作在 invoke 中执行。 fn signature(&self) -> Option { Some(endor_core::CallSignature::exact( vec![endor_core::AnnotationType::Number], endor_core::AnnotationType::Any, endor_core::CallEffect::ReadOnly, )) } /// 绑定列表接收者已捕获,只接受一个显式索引。 fn arity(&self) -> Option { Some(endor_core::CallArity::exact(1)) } /// 要求索引可精确转为非负平台整数,检查列表范围后返回对应托管成员,不复制成员对象。 fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result { // VM 调用已绑定 at 时仍检查实参数形状,不能依赖调用方一定执行签名预检查。 let [Value::Number(index)] = args else { return Err(VmError::InvalidArgument("object list at expects an index")); }; // 先要求可精确表示为 i64 的整数,再转为 usize,拒绝小数、负数及目标平台越界值。 let index = index .as_i64() .and_then(|v| usize::try_from(v).ok()) .ok_or(VmError::InvalidArgument("invalid list index"))?; // 根据已验证平台索引借用真实成员句柄;越界返回错误,不像 first 空列表那样返回 Nil。 let mount = *cx .store() .payload::(self.0, "Object list")? .0 .get(index) .ok_or(VmError::InvalidArgument("list index out of range"))?; // Return 交回原托管身份,由 VM 的 native 返回协议处理结果拥有关系,不复制成员对象。 Ok(NativeResult::Return(Value::Object(mount))) } } #[derive(Debug)] // append/remove/extend/added 共用的绑定适配器,操作差异由三个标志决定。 struct Edit { // 当前列表接收者,方法 captures 强持有它,列表属性再指回方法形成可追踪托管环。 owner: ObjectRef, // 新列表/函数的名义类型从该桌面解析,不误接到其他 VM 的类型注册表。 desktop: ObjectRef, // true 时把唯一实参解释为删除索引;否则是追加成员(extend 优先)。 remove: bool, // added 返回新列表而不改原接收者;原地操作则更新 payload/count/first。 persistent: bool, // true 时唯一实参须为另一 ObjectList,将其成员按顺序追加。 extend: bool, } impl NativeCallable for Edit { // 绑定接收者已经捕获,四种编辑操作都只接收一个显式参数。 fn arity(&self) -> Option { Some(endor_core::CallArity::exact(1)) } // 由 VM 调用绑定编辑方法;先在 Vec 副本中验证/编辑,成功后才更新原列表或分配返回列表。 fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result { // 复制借用成员句柄以结束原列表借用;改副本本身不会修改 VM 拥有边。 let mut members = items(cx.store(), self.owner)?.to_vec(); // extend 接收对象列表,不把整个列表作为一个成员追加;先检查合并后的总预算。 if self.extend { // 要求实参为对象,再由 items 验证确为宿主 ObjectList payload。 let [Value::Object(other)] = args else { return Err(VmError::InvalidArgument("extend expects ObjectList")); }; // 借用另一列表当前成员;owner==other 时复制副本已建好,仍可安全按原序列扩展。 let incoming = items(cx.store(), *other)?; // 合并后不能超过 10000,即使每个输入列表单独都合法也须重新检查。 if members.len() + incoming.len() > 10_000 { return Err(VmError::InvalidArgument( "object list exceeds 10000 members", )); } members.extend_from_slice(incoming); } else if self.remove { // 删除路径只修改指定索引,不按业务身份去重或删除所有相同成员。 // 索引类型必须为 Number,后面再拒绝小数、负数和越界值。 let [Value::Number(index)] = args else { return Err(VmError::InvalidArgument("remove expects index")); }; // 先精确转 i64/usize,再比较现有长度;无效索引不改变原 payload。 let index = index .as_i64() .and_then(|v| usize::try_from(v).ok()) .filter(|&i| i < members.len()) .ok_or(VmError::InvalidArgument("list index out of range"))?; members.remove(index); } else { // 单项追加只接受对象句柄,不能装入 Nil、字符串或数字作为宿主成员。 let [Value::Object(item)] = args else { return Err(VmError::InvalidArgument("append expects object")); }; // 追加前已有 10000 项时拒绝,不能先写原列表再报预算错误。 if members.len() >= 10_000 { return Err(VmError::InvalidArgument( "object list exceeds 10000 members", )); } members.push(*item); } // added 的持久式结果使用默认 ObjectList 类型,原列表(含其子类字段)保持不变。 if self.persistent { return Ok(NativeResult::Return(Value::Object(collection( cx, members, self.desktop, )?))); } // 原地路径重新计算元信息,payload 替换与 count/first 属性写入都经过 VM 写屏障。 let count = members.len(); // 空列表 first=Nil;非空保存第一个原成员身份,不构造副本。 let first = members .first() .copied() .map(Value::Object) .unwrap_or(Value::Nil); // 替换 payload 让 VM 更新成员引用追踪并产生 Contents 变化,依赖消费者可观察编辑。 cx.replace_payload(self.owner, ObjectList(members))?; cx.set_attr(self.owner, "count", Value::Number((count as i64).into()))?; cx.set_attr(self.owner, "first", first)?; Ok(NativeResult::Return(Value::Nil)) } } /// 将已有对象引用包装为新的 ObjectList,并安装默认名义类型及下标查询方法。 pub fn collection( cx: &mut NativeContext<'_>, mounts: Vec, desktop: ObjectRef, ) -> Result { // 宿主返回的集合不带源子类字段,统一使用桌面的默认 ObjectList 名义类型。 build(cx, mounts, desktop, None) } /// 建立列表 payload 和 first/count/at 属性;definition 指定子类时保留该名义类型。 /// 最多接受 10000 个成员;空列表的 first 为 Nil,at 越界仍报错而不返回 Nil。 fn build( cx: &mut NativeContext<'_>, mounts: Vec, desktop: ObjectRef, definition: Option, ) -> Result { // 构造入口也检查预算,不能只在编辑入口检查而让宿主一次生成超大列表。 if mounts.len() > 10_000 { return Err(VmError::InvalidArgument( "object list exceeds 10000 members", )); } // 在移动 Vec 之前提取元信息;成员对象的拥有边由 ObjectList::trace 声明。 let first = mounts .first() .copied() .map(Value::Object) .unwrap_or(Value::Nil); // 在成员 Vec 移入 payload 之前保存数量,供普通 count 属性使用。 let count = mounts.len(); // ObjectList payload 声明全部成员强拥有边,VM 分配负责登记引用。 let set = cx.allocate(ObjectList(mounts))?; // 源子类构造保留请求的名义类型;普通 collection 返回值才用默认类型。 if let Some(definition) = definition { cx.attach_type(set, definition)?; } else { crate::ui_bridge::attach_desktop_type(cx, set, "ObjectList", desktop)?; } cx.set_attr(set, "first", first)?; cx.set_attr(set, "count", Value::Number((count as i64).into()))?; // at 捕获列表,列表属性又指向 at,形成托管环;显式 captures 让 GC 能追踪和回收此环。 let at = cx.allocate(ContextNativeFunction::new("at", At(set), vec![set]))?; crate::ui_bridge::attach_desktop_type(cx, at, "Function", desktop)?; cx.set_attr(set, "at", Value::Object(at))?; // 逐个安装四种绑定操作,标志表是方法语义的唯一分派来源。 for (name, remove, persistent, extend) in [ ("append", false, false, false), ("remove", true, false, false), ("added", false, true, false), ("extend", false, false, true), ] { // 显式 captures=[set,desktop] 保活接收者与类型环境,循环可由 Aster GC 看到。 let method = cx.allocate(ContextNativeFunction::new( name, Edit { owner: set, desktop, remove, persistent, extend, }, vec![set, desktop], ))?; crate::ui_bridge::attach_desktop_type(cx, method, "Function", desktop)?; cx.set_attr(set, name, Value::Object(method))?; } Ok(set) } /// 借用受检查列表的条目;返回值的有效期仍依赖原托管对象存活。 pub fn items(store: &endor_core::ObjectStore, list: ObjectRef) -> Result<&[ObjectRef], VmError> { Ok(&store.payload::(list, "ObjectList")?.0) } #[derive(Debug)] /// ObjectList 原生类型的可调用构造器,负责检查传入子对象。 pub struct Constructor { // bootstrap 提供的可选构造契约,子类构造仍在 invoke 按字段后缀切分参数。 pub signature: Option, // 构造列表与绑定方法的所属桌面,captures 由注册构造器的 bootstrap 负责保活。 pub desktop: ObjectRef, } impl NativeCallable for Constructor { /// 给出参数与返回值的托管类型契约;实际操作在 invoke 中执行。 fn signature(&self) -> Option { self.signature.clone() } /// 至少包含请求 Type,之后为可变成员对象与源子类字段,具体分段在 invoke 校验。 fn arity(&self) -> Option { Some(endor_core::CallArity::range(1, usize::MAX)) } /// 按请求类型字段数切分成员/字段,禁止覆盖列表协议保留属性,验证后分配列表并初始化源字段。 fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result { // VM 类型调用传入请求 Type 为首参,允许构造 ObjectList 名义子类。 let [Value::Object(definition), values @ ..] = args else { return Err(VmError::InvalidArgument("ObjectList type argument")); }; // 首参是要构造的名义类型,余下实参分成“成员对象前缀”和“子类字段后缀”。 let fields = cx .store() .payload::(*definition, "Type")? .fields() .to_vec(); // 列表查询和修改方法均由原生协议保留,子类字段不得覆盖这些名称。 if fields.iter().any(|field| { matches!( field.name.as_str(), "count" | "first" | "at" | "append" | "remove" | "added" | "extend" ) }) { return Err(VmError::InvalidArgument("reserved ObjectList field")); } // 字段按类型定义顺序占据最后 N 项;checked_sub 在实参不足时返回错误,避免下标下溢。 let count = values .len() .checked_sub(fields.len()) .ok_or(VmError::InvalidArgument( "missing ObjectList subclass fields", ))?; cx.store() .check_class_field_arguments(*definition, &values[count..])?; // 字段值先按声明检查;成员前缀另行要求全部是对象,禁止把标量装入宿主对象列表。 let members = values[..count] .iter() .map(|value| { // 只检查成员前缀的每项是对象;源子类标量字段已经从后缀单独校验。 if let Value::Object(object) = value { Ok(*object) } else { Err(VmError::InvalidArgument( "ObjectList members must be objects", )) } }) .collect::, _>>()?; // 所有字段/成员验证完成后才分配,避免坏字段参数先产生原生列表副作用。 let list = build(cx, members, self.desktop, Some(*definition))?; // 按声明顺序初始化子类字段,保留契约检查后的原 Value 身份。 for (field, value) in fields.iter().zip(&values[count..]) { cx.set_attr(list, &field.name, value.clone())?; } Ok(NativeResult::Return(Value::Object(list))) } }