Validate declared class fields at construction and attribute writes

This commit is contained in:
2026-10-22 11:44:09 +08:00
parent 6346041f77
commit 20e29e3c5a
13 changed files with 342 additions and 26 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# 编译链路检查
[classes.rs](classes.rs) 覆盖源码 class、NativeTyped 实际原生载荷继承和多层字段构造、继承字段构造、动态方法覆盖、绑定接收者保活/回收、绑定后参数数量和重复成员/缺少 self 的预执行诊断。只编译,不在本地运行。
[classes.rs](classes.rs) 覆盖源码 class、NativeTyped 实际原生载荷继承和多层字段构造、声明字段检查的构造/写入/继承/环境/GC、继承字段构造、动态方法覆盖、绑定接收者保活/回收、绑定后参数数量和重复成员/缺少 self 的预执行诊断。只编译,不在本地运行。
[pipeline.rs](pipeline.rs) 将真实 Endor 示例交给 reader/parser/名称分析/代码生成/VM,检查函数值、前向引用、局部遮蔽、精确数字常量、语义诊断、宿主全局与属性回调、对象结果保活、跨 VM 模板装载、源码签名保留和参数/返回失败清理、反复调用、错误帧清理、递归限额、共享交互命名空间及超过 255 的操作数。
+150 -1
View File
@@ -40,6 +40,7 @@ impl NativeCallable for WidgetFactory {
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()))?;
cx.attach_type(instance, *requested)?;
for (field, value) in fields.iter().zip(values) {
@@ -52,6 +53,7 @@ impl NativeCallable for WidgetFactory {
#[test]
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();
let roots = vm.bootstrap_types().unwrap();
let factory = vm
@@ -79,6 +81,15 @@ fn source_subclasses_preserve_native_allocation_fields_and_nominal_identity() {
execute(&mut vm, globals, source, ParseMode::Module),
Value::Nil
);
assert!(matches!(
try_execute(
&mut vm,
globals,
r#"Grand("native", "bad", "extra")"#,
ParseMode::Expression
),
Err(VmError::TypeMismatch { .. })
));
let value = execute(
&mut vm,
globals,
@@ -111,6 +122,15 @@ fn source_subclasses_preserve_native_allocation_fields_and_nominal_identity() {
}
fn execute(vm: &mut Vm, globals: ObjectRef, source: &str, mode: ParseMode) -> Value {
try_execute(vm, globals, source, mode).unwrap()
}
fn try_execute(
vm: &mut Vm,
globals: ObjectRef,
source: &str,
mode: ParseMode,
) -> Result<Value, VmError> {
let names = vm
.dict_items(globals)
.unwrap()
@@ -126,7 +146,7 @@ fn execute(vm: &mut Vm, globals: ObjectRef, source: &str, mode: ParseMode) -> Va
let source = EndorReader::from_text("<classes>", source);
let template = compile_source(&source, mode, &CompileOptions { globals: names }).unwrap();
let code = template.load(vm).unwrap();
let result = vm.execute(code, globals).unwrap();
let result = vm.execute(code, globals);
vm.release(code).unwrap();
result
}
@@ -221,3 +241,132 @@ fn duplicate_members_and_missing_receivers_fail_before_execution() {
);
}
}
#[test]
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();
let roots = vm.bootstrap_types().unwrap();
vm.dict_set(
globals,
Value::Text("Object".into()),
Value::Object(roots.object),
)
.unwrap();
vm.release(roots.object).unwrap();
vm.release(roots.type_type).unwrap();
execute(
&mut vm,
globals,
r#"
class Base { count: Number }
class Child: Base { label: String }
value = Child(9, "ok")
class Holder { child: Base }
holder = Holder(value)
class Unknown { value: Missing }
"#,
ParseMode::Interactive,
);
let Value::Object(value) = vm
.dict_get(globals, &Value::Text("value".into()))
.unwrap()
.unwrap()
else {
panic!("instance")
};
let version = vm.object_store().change_version(value).unwrap();
for source in [
r#"Child("bad", "ok")"#,
"Child(9, 2)",
"Holder(1)",
"Unknown(1)",
r#"value.count = "bad""#,
] {
assert!(matches!(
try_execute(&mut vm, globals, source, ParseMode::Interactive),
Err(VmError::TypeMismatch { .. } | VmError::UnknownAnnotation(_))
));
}
assert_eq!(
vm.get_attr(value, "count").unwrap(),
Value::Number(9.into())
);
assert_eq!(vm.object_store().change_version(value).unwrap(), version);
assert!(vm.del_attr(value, "count").is_err());
vm.set_attr(value, "count", Value::Number(10.into()))
.unwrap();
execute(&mut vm, globals, "class Base {}", ParseMode::Interactive);
// Other nominal names resolve dynamically in the declaring environment.
assert!(matches!(
try_execute(&mut vm, globals, "Holder(value)", ParseMode::Expression),
Err(VmError::TypeMismatch { .. })
));
// 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]
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();
let roots = vm.bootstrap_types().unwrap();
vm.dict_set(
globals,
Value::Text("Object".into()),
Value::Object(roots.object),
)
.unwrap();
vm.release(roots.object).unwrap();
vm.release(roots.type_type).unwrap();
// Object permits nil, so a separate numeric field provides an initial instance.
execute(
&mut vm,
globals,
r#"
class Link { next: Link }
class Seed: Link {}
"#,
ParseMode::Interactive,
);
// A recursive nonnullable field can be initialized by the trusted host in two steps.
let Value::Object(link) = vm
.dict_get(globals, &Value::Text("Link".into()))
.unwrap()
.unwrap()
else {
panic!("class")
};
let seed = vm.new_object().unwrap();
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))
.unwrap();
execute(
&mut vm,
globals,
"class Link { value: Number }",
ParseMode::Interactive,
);
vm.set_attr(seed, "next", Value::Object(seed)).unwrap();
assert!(matches!(
vm.set_attr(seed, "next", Value::Nil),
Err(VmError::TypeMismatch { .. })
));
vm.set_source_type_checks(false).unwrap();
vm.set_attr(seed, "next", Value::Nil).unwrap();
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);
}
+2 -2
View File
@@ -5,9 +5,9 @@
| 文件 | 职责 |
| --- | --- |
| [lib.rs](lib.rs) | VM 类型导出、objects 类型兼容重导出、宿主调用与字节码 rustdoc 示例 |
| [vm.rs](vm.rs) | Vm 拥有 ObjectStore 与错误位置记录;对象操作和宿主 new_class 和显式Trait注册/关系查询显式委托给 objects;源码检查开关与函数类型配置 |
| [vm.rs](vm.rs) | Vm 拥有 ObjectStore 与错误位置记录;对象操作和宿主 new_class 和显式Trait注册/关系查询显式委托给 objects;函数/字段检查统一开关与函数类型配置 |
| [frame.rs](frame.rs) | EndorFrame、栈条目、返回类型执行根、执行位置元数据、ExecutionLimits |
| [eval.rs](eval.rs) | execute/call、只读调用与依赖结果、参数绑定、可选注解检查、帧切换、取指分派、MakeFunction、结果根转移与失败清理 |
| [eval.rs](eval.rs) | execute/call、只读调用与依赖结果、参数绑定、可选注解检查、源码类字段环境绑定、帧切换、取指分派、MakeFunction、结果根转移与失败清理 |
## 常见修改入口
+2 -1
View File
@@ -501,7 +501,8 @@ impl Vm {
_ => Err(VmError::ObjectKindMismatch("class method")),
})
.collect::<Result<std::collections::BTreeMap<_, _>, _>>()?;
self.objects.new_class(name, base, fields, methods)
self.objects
.new_class_with_scope(name, base, fields, methods, Some(frame.function))
}
fn eval_frames(
+1
View File
@@ -21,6 +21,7 @@ impl Vm {
"configure source type checks during read-only call",
));
}
self.objects.set_field_type_checks(enabled)?;
self.source_type_checks = enabled;
Ok(())
}
+2 -2
View File
@@ -6,13 +6,13 @@
| --- | --- |
| [lib.rs](lib.rs) | 包边界、对象类型/ObjectStore 公开导出(含Trait/MethodRequirement/SourceSignature/AnnotationType)与独立存储 rustdoc 示例 |
| [object.rs](object.rs) | Value、CompareOp、ObjectPayload(含显式函数标记)、ObjectHeader、EndorObject、Record、NativeFunction |
| [types.rs](types.rs) | 托管名义类型、显式 Object/Type 引导、继承查询、受管方法表/接收者绑定、类字段、显式Trait定义/声明/方法参数约束、原生构造委托、注解解析/匹配 |
| [types.rs](types.rs) | 托管名义类型、显式 Object/Type 引导、继承查询、受管方法表/接收者绑定、类字段、显式Trait定义/声明/方法参数约束、原生构造委托、声明环境托管边、字段构造/写入检查与注解解析/匹配 |
| [native.rs](native.rs) | NativeContext、上下文原生函数、作用域语言函数工厂、显式能力声明入口、函数标记、根与非递归委托调用 |
| [changes.rs](changes.rs) | 合并变更批次、变更键与有界通知额度 |
| [number.rs](number.rs) | Integer/Decimal/Rational/BinaryFloat/Complex 的无损表示和装箱载荷 |
| [builtins.rs](builtins.rs) | String、Dict、Cell、Function、BaseException 载荷与 trace 边、语言函数标记 |
| [heap.rs](heap.rs) | 私有 Heap、不可伪造 ObjectRef、稳定 Box、引用事务、迭代释放、循环回收、后代契约更新的私有借用枚举、同类型附加幂等、只读变更屏障/全调用及嵌套子调用读取跟踪及私有边界测试 |
| [store.rs](store.rs) | ObjectStore、空堆创建、对象操作、retain/release 和回收入口、弱 Function 类型配置与分配分类 |
| [store.rs](store.rs) | ObjectStore、空堆创建、字段检查配置/属性写入屏障、对象操作、retain/release 和回收入口、弱 Function 类型配置与分配分类 |
| [store_builtins.rs](store_builtins.rs) | 载荷校验、代码/函数工厂、Dict/Cell 操作及键语义 |
| [instruction.rs](instruction.rs) | Opcode、Instruction、wordcode 编解码、MakeFunction/比较参数与跳转读取接口 |
| [code.rs](code.rs) | CodeLayout、EndorCode/EndorCodeObject、可选源码签名、共享不可变代码和操作数/控制流/栈验证 |
+19
View File
@@ -11,9 +11,21 @@ use crate::{
pub struct ObjectStore {
pub(crate) heap: Heap,
function_type: Option<WeakObjectRef>,
pub(crate) field_type_checks: bool,
}
impl ObjectStore {
/// Opt-in declared-field checks, shared by VM and NativeContext writes.
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",
));
}
self.field_type_checks = enabled;
Ok(())
}
/// Existing objects are immutable; fresh result graphs may be constructed.
/// Scopes must end in LIFO order. Read handles are borrowed, never GC roots.
pub fn begin_read_only(&mut self) -> usize {
@@ -129,6 +141,7 @@ impl ObjectStore {
Ok(Self {
heap: Heap::new()?,
function_type: None,
field_type_checks: false,
})
}
@@ -280,6 +293,9 @@ impl ObjectStore {
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)?;
}
let storage = *self
.attribute_path(reference)?
.last()
@@ -288,6 +304,9 @@ impl ObjectStore {
}
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"));
}
+142 -13
View File
@@ -21,7 +21,7 @@ pub enum TypeConstructor {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClassField {
pub name: String,
/// Preserved source annotation, like current function annotations.
/// Source annotation resolved at checked construction/field writes.
pub type_name: String,
}
@@ -35,6 +35,8 @@ pub struct EndorTypeObject {
constructor: TypeConstructor,
methods: BTreeMap<String, ObjectRef>,
fields: Vec<ClassField>,
// Declaring execution environment; a managed edge, never an external root.
field_scope: Option<ObjectRef>,
traits: Vec<ObjectRef>,
}
@@ -69,6 +71,9 @@ impl ObjectPayload for EndorTypeObject {
{
visit(function);
}
if let Some(scope) = self.field_scope {
visit(scope);
}
for capability in &self.traits {
visit(*capability);
}
@@ -121,6 +126,8 @@ impl NativeCallable for Construct {
});
}
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)?;
for (field, value) in fields.iter().zip(arguments) {
@@ -149,6 +156,7 @@ impl ObjectStore {
constructor: TypeConstructor::Record,
methods: BTreeMap::new(),
fields: Vec::new(),
field_scope: None,
traits: Vec::new(),
})?;
let mut allocated_type = None;
@@ -159,6 +167,7 @@ impl ObjectStore {
constructor: TypeConstructor::Abstract,
methods: BTreeMap::new(),
fields: Vec::new(),
field_scope: None,
traits: Vec::new(),
})?;
allocated_type = Some(type_type);
@@ -200,6 +209,7 @@ impl ObjectStore {
constructor,
methods: BTreeMap::new(),
fields,
field_scope: None,
traits: Vec::new(),
})?;
if let Err(error) = self.attach_type(definition, metatype) {
@@ -284,6 +294,7 @@ impl ObjectStore {
constructor: definition.constructor,
methods,
fields: definition.fields.clone(),
field_scope: definition.field_scope,
traits: definition.traits.clone(),
};
self.heap.replace_payload(type_object, updated)
@@ -299,6 +310,23 @@ impl ObjectStore {
own_fields: Vec<ClassField>,
methods: BTreeMap<String, ObjectRef>,
) -> Result<ObjectRef, VmError> {
self.new_class_with_scope(name, base, own_fields, methods, None)
}
/// Source class builder with a traced function environment for field names.
/// None supports primitive annotations and the declaring class itself.
pub fn new_class_with_scope(
&mut self,
name: impl Into<String>,
base: ObjectRef,
own_fields: Vec<ClassField>,
methods: BTreeMap<String, ObjectRef>,
scope: Option<ObjectRef>,
) -> Result<ObjectRef, VmError> {
if let Some(scope) = scope {
self.payload::<crate::EndorFunctionObject>(scope, "function")?;
}
let field_scope = if own_fields.is_empty() { None } else { scope };
let definition = self.payload::<EndorTypeObject>(base, "Type")?;
if matches!(definition.constructor, TypeConstructor::Native(_)) {
return Err(VmError::UnsupportedOperation(
@@ -309,6 +337,7 @@ impl ObjectStore {
let mut fields = definition.fields.clone();
for field in own_fields {
if field.name.is_empty()
|| field.type_name.is_empty()
|| field.name == "type"
|| fields.iter().any(|old| old.name == field.name)
|| self.lookup_method(base, &field.name)?.is_some()
@@ -340,6 +369,7 @@ impl ObjectStore {
constructor,
methods,
fields,
field_scope,
traits: Vec::new(),
})?;
if let Err(error) = self
@@ -754,6 +784,116 @@ impl AnnotationType {
}
}
impl ObjectStore {
fn primitive_annotation(name: &str) -> Option<AnnotationType> {
match name {
"Object" => Some(AnnotationType::Any),
"Unit" => Some(AnnotationType::Unit),
"Number" => Some(AnnotationType::Number),
"String" | "Text" => Some(AnnotationType::String),
"Bool" => Some(AnnotationType::Bool),
"Function" => Some(AnnotationType::Function),
"Type" => Some(AnnotationType::Type),
"Trait" => Some(AnnotationType::Trait),
_ => None,
}
}
pub(crate) fn declared_field(
&self,
object: ObjectRef,
name: &str,
) -> Result<Option<(ObjectRef, &ClassField)>, VmError> {
self.declared_field_in_type(self.type_of(object)?, name)
}
fn declared_field_in_type(
&self,
mut current: Option<ObjectRef>,
name: &str,
) -> Result<Option<(ObjectRef, &ClassField)>, VmError> {
while let Some(definition) = current {
let data = self.payload::<EndorTypeObject>(definition, "Type")?;
let inherited = match data.base {
Some(base) => self.payload::<EndorTypeObject>(base, "Type")?.fields.len(),
None => 0,
};
if let Some(field) = data.fields[inherited..]
.iter()
.find(|field| field.name == name)
{
return Ok(Some((definition, field)));
}
current = data.base;
}
Ok(None)
}
pub(crate) fn check_field_write(
&self,
object: ObjectRef,
name: &str,
value: &Value,
) -> Result<(), VmError> {
let Some((owner, field)) = self.declared_field(object, name)? else {
return Ok(());
};
self.check_field_value(owner, field, value)
}
/// NativeTyped allocators can validate their field suffix before any effects.
/// Count is always checked; annotation matching follows the opt-in setting.
pub fn check_class_field_arguments(
&self,
definition: ObjectRef,
arguments: &[Value],
) -> Result<(), VmError> {
let data = self.payload::<EndorTypeObject>(definition, "Type")?;
if arguments.len() != data.fields.len() {
return Err(VmError::ArgumentCount {
name: data.name.clone(),
required: data.fields.len(),
maximum: data.fields.len(),
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");
self.check_field_value(owner, field, value)?;
}
}
Ok(())
}
fn check_field_value(
&self,
owner: ObjectRef,
field: &ClassField,
value: &Value,
) -> Result<(), VmError> {
let definition = self.payload::<EndorTypeObject>(owner, "Type")?;
let expected = if field.type_name == definition.name {
AnnotationType::Nominal(owner)
} else if let Some(primitive) = Self::primitive_annotation(&field.type_name) {
primitive
} else if let Some(scope) = definition.field_scope {
self.annotation_type(scope, &field.type_name, None)?
} else {
return Err(VmError::UnknownAnnotation(field.type_name.clone()));
};
if !self.matches_annotation(value, expected)? {
return Err(VmError::TypeMismatch {
function: definition.name.clone(),
position: format!("field {}", field.name),
expected: field.type_name.clone(),
actual: self.value_type_name(value)?,
});
}
Ok(())
}
pub fn annotation_type(
&self,
function: ObjectRef,
@@ -781,18 +921,7 @@ impl ObjectStore {
current = payload.base;
}
}
let primitive = match name {
"Object" => Some(AnnotationType::Any),
"Unit" => Some(AnnotationType::Unit),
"Number" => Some(AnnotationType::Number),
"String" | "Text" => Some(AnnotationType::String),
"Bool" => Some(AnnotationType::Bool),
"Function" => Some(AnnotationType::Function),
"Type" => Some(AnnotationType::Type),
"Trait" => Some(AnnotationType::Trait),
_ => None,
};
if let Some(primitive) = primitive {
if let Some(primitive) = Self::primitive_annotation(name) {
return Ok(primitive);
}
let value = self
+1 -1
View File
@@ -6,7 +6,7 @@
| 解释执行、资源修订与引擎升级 | [执行与版本](architecture/execution-and-versions.md) | frontend/compiler/core |
| EndorObject、句柄、默认操作 | [对象模型](runtime/objects.md) | objects/src |
| 原生上下文、只读调用、子调用读取依赖、委托与变更通知 | [原生调用](runtime/native-calls.md) | objects/src/native.rs、changes.rs、core/src/eval.rs |
| 托管类型、宿主类注册、原生分配继承、字段存储、显式能力、函数分类与可选源码注解检查 | [名义类型](runtime/types.md) | objects/src/types.rs |
| 托管类型、宿主类注册、原生分配继承、字段存储、显式能力、函数分类、声明字段与可选源码注解检查 | [名义类型](runtime/types.md) | objects/src/types.rs |
| Number、String、Dict、Cell、Function | [基本对象](runtime/builtins.md) | objects/src |
| 源码读取与来源 | [Reader](runtime/reader.md) | frontend/src/reader.rs |
| 词法、语法、裸赋值和诊断 | [Frontend](runtime/frontend.md) | frontend/src/lexer.peg/grammar.peg |
+1 -1
View File
@@ -21,7 +21,7 @@ Aster 是可嵌入引擎,只保留 objects、core、frontend、compiler。Obje
## 当前缺口
平台资源版本/依赖锁、源码闭包、数值算术、完整异常传播、内存配额、可靠取消、不可信代码沙箱、自动 GC 和宿主 panic 后执行根恢复尚未完成。源码函数参数/返回注解已支持默认关闭的运行时检查,Tessera 显式启用;普通名称按调用时函数全局解析,字段/静态类型/效果与原生统一签名仍未完成,见[类型机制](../runtime/types.md#源码函数注解检查)。不引入 Python 元类。UI 接入适配由 Tessera 实现。通用 ObjectPayload::assign_from 允许宿主对象选择共享引用赋值;协议和根计数归 Aster,UI 挂载、布局、绘制仍归 Tessera,见字节码 VM 的共享引用章节。
平台资源版本/依赖锁、源码闭包、数值算术、完整异常传播、内存配额、可靠取消、不可信代码沙箱、自动 GC 和宿主 panic 后执行根恢复尚未完成。源码函数参数/返回注解已支持默认关闭的运行时检查,Tessera 显式启用;普通名称按调用时函数全局解析,声明字段构造/写入检查已接入同一可选开关;完整初始化、静态类型/效果与原生统一签名仍未完成,见[类型机制](../runtime/types.md#源码函数注解检查)。不引入 Python 元类。UI 接入适配由 Tessera 实现。通用 ObjectPayload::assign_from 允许宿主对象选择共享引用赋值;协议和根计数归 Aster,UI 挂载、布局、绘制仍归 Tessera,见字节码 VM 的共享引用章节。
## 工作约定
+1 -1
View File
@@ -2,7 +2,7 @@
| 文档 | 内容 |
| --- | --- |
| [types.md](types.md) | 托管 Object/Type、名义继承、构造与类型边生命周期 |
| [types.md](types.md) | 托管 Object/Type、名义继承、声明字段/函数注解检查、构造与类型边生命周期 |
| [objects.md](objects.md) | 对象、句柄与默认操作 |
| [native-calls.md](native-calls.md) | 原生上下文、非递归委托、弱引用、托管赋值与变更批次 |
| [builtins.md](builtins.md) | Number、String、Dict、Cell、Function、异常数据 |
+2
View File
@@ -64,3 +64,5 @@ take_changes 按对象与变更键合并同一批写入,返回最终版本。
通过 replace_payload、属性或容器事务更新;载荷内部自行变动不会自动通知。
远端回归用例见 [native_context.rs](../../crates/core/tests/native_context.rs);本地不执行测试。
声明字段检查启用时,NativeContext::set_attr 使用同一字段写入屏障。NativeTyped 分配器应在副作用前调用 ObjectStore::check_class_field_arguments 检查自己的字段参数切片,具体参数组合仍由宿主定义;配置、声明环境与生命周期的唯一契约见[名义类型](types.md#声明字段注解检查)。
+18 -3
View File
@@ -56,7 +56,7 @@ class DetailedCounter: Counter {
方法表本身是类型对象拥有的托管边。宿主 define_method 可显式替换条目,不改变类型身份、基类或已有绑定方法选中的函数。bind_attr 返回一份拥有式根;get_attr 返回借用值和未绑定方法,宿主不能把两者的调用/释放协议混用。positional_parameter_count 会扣除绑定的接收者,供宿主判断事件回调的剩余源码参数。
这是一份实现子集说明:构造器只按字段赋值,没有默认值、命名字段构造或隐式 init;字段注解保留名称但尚未约束字段写入;源码参数/返回注解支持下述宿主可选的运行时检查。源码类可继承 Record 或 NativeTyped 分配器;未选择支持继承的旧 Native 分配器仍明确报错。继承会保留分配器和父类字段,不能伪造带原生名义身份的普通 Record。字段构造参数如何与原生参数组合由宿主分配器负责;Tessera 使用原生参数在前、继承及本类字段在后的协议。trait 的正式源码语法、完整类型兼容性和效果系统仍待实现;宿主显式能力子集见下文。正式语言文档的候选语法不因这一实现被自动确认为规范。
这是一份实现子集说明:构造器只按字段赋值,没有默认值、命名字段构造或隐式 init;字段注解在启用运行时检查后约束构造及字段写入,见下文;源码参数/返回注解支持下述宿主可选的运行时检查。源码类可继承 Record 或 NativeTyped 分配器;未选择支持继承的旧 Native 分配器仍明确报错。继承会保留分配器和父类字段,不能伪造带原生名义身份的普通 Record。字段构造参数如何与原生参数组合由宿主分配器负责;Tessera 使用原生参数在前、继承及本类字段在后的协议。trait 的正式源码语法、完整类型兼容性和效果系统仍待实现;宿主显式能力子集见下文。正式语言文档的候选语法不因这一实现被自动确认为规范。
## 可转移的字段存储
@@ -81,7 +81,7 @@ implement_trait(Type, Trait)明确建立能力关系并校验必需方法。只
## 源码函数注解检查
`Vm::set_source_type_checks(true)` 启用源码函数调用的运行时检查,默认关闭;Tessera 显式启用,其他宿主须自行选择。Compiler 把形参注解、返回注解及可选的声明类名保存为 `SourceSignature`,模板装载后仍保留。宿主也可通过 `EndorCode::with_source_signature` 添加经过形参数量/非空名称校验的元数据;没有该元数据的宿主生成代码继续沿原调用协议执行。
`Vm::set_source_type_checks(true)` 启用源码函数调用及声明字段写入的运行时检查,默认关闭;Tessera 显式启用,其他宿主须自行选择。Compiler 把形参注解、返回注解及可选的声明类名保存为 `SourceSignature`,模板装载后仍保留。宿主也可通过 `EndorCode::with_source_signature` 添加经过形参数量/非空名称校验的元数据;没有该元数据的宿主生成代码继续沿原调用协议执行。
每次调用在进入函数体前检查所有实参与用到的默认参数,并解析本次返回约束;`ReturnValue` 在交出结果前检查返回值。错误分别是 `UnknownAnnotation` 或带函数、参数/return、预期及实际名称的 `TypeMismatch`。参数失败不进入函数体;返回失败不回滚此前完成的业务写入。执行帧持有返回约束中实际 Type/Trait 的一份根,直到成功或失败清理;全局名字在调用中改变也不会释放这一约束。约束查询不写函数或类型对象,适用于只读布局/绘制调用。
@@ -96,6 +96,21 @@ implement_trait(Type, Trait)明确建立能力关系并校验必需方法。只
| Type/Trait | 真实 Type/Trait 定义载荷 |
| 其他名称 | 从函数自己的 globals、其次创建时捕获的 builtins 找到实际 Type 或 Trait;前者检查名义实例/继承,后者检查显式能力 |
普通名称每次调用重新解析,不是静态推断或定义时冻结的类型绑定。同名新定义不等于旧定义;自由函数可观察后续全局类型名重绑定。方法注解中指向自身声明类的名称,优先从实际接收者祖先中查找仍持有该函数的方法定义,因此 REPL 重定义全局类名后,原实例保存的绑定方法仍可调用。其他类型名仍遵循上述全局解析规则。此机制不提供重写方差、字段赋值检查、泛型、效果类型或原生函数的统一签名元数据。
普通名称每次调用重新解析,不是静态推断或定义时冻结的类型绑定。同名新定义不等于旧定义;自由函数可观察后续全局类型名重绑定。方法注解中指向自身声明类的名称,优先从实际接收者祖先中查找仍持有该函数的方法定义,因此 REPL 重定义全局类名后,原实例保存的绑定方法仍可调用。其他类型名仍遵循上述全局解析规则。此机制不提供重写方差、泛型、效果类型或原生函数的统一签名元数据。字段赋值检查见下文。
源码与原生函数的统一名义分类由 `ObjectStore::set_function_type`/`Vm::set_function_type` 显式配置。配置验证 Type、拒绝只读修改,只保留弱引用且不可改成另一个定义;设置后新分配的显式 `ObjectPayload::is_function` 载荷自动附加该类型,已有对象不追溯改写。语言函数、两类原生函数及绑定方法提供此标记;仅有 call 能力不算函数。配置类型过期则函数分配报错并清理新对象,不引入隐藏 GC 根。宿主应在创建函数前配置,并通过正常对象边保活定义。
## 声明字段注解检查
`Vm::set_source_type_checks(true)` 同时启用 ObjectStore 的声明字段检查;独立存储宿主也可调用 `ObjectStore::set_field_type_checks(true)`,默认关闭。配置在只读作用域内被拒绝。启用不会追溯扫描已有对象,只检查此后发生的字段写入。
Record 构造在分配前校验全部位置字段。NativeTyped 分配器通过 `check_class_field_arguments(definition, values)` 在自身副作用前检查字段参数;方法始终检查数量,注解匹配遵循开关。字段参数与原生参数如何组合仍由分配器定义;Tessera 在原生参数之后放字段参数并在创建原生对象前检查。所有经 ObjectStore/Vm/NativeContext 的 `set_attr` 写入,包括 StoreAttr,按实际对象类型查找字段声明,在最终 attribute_target 存储变更前检查。失败保持该字段值、引用边和版本不变;不回滚先前完成的其他操作。启用后 `del_attr` 拒绝删除声明字段;未声明的动态属性沿用现有行为。
源码 BuildClass 把声明执行函数作为 Type 的托管强边保存,用其 globals 与创建时 builtins 解析字段注解;声明环境与实例/类型环由普通循环回收处理,没有隐藏外部根。`ObjectStore::new_class_with_scope(..., Some(function))` 提供相同的宿主入口。无环境的 `new_class` 支持上述基本注解及本类名;其他名称报 UnknownAnnotation,宿主可显式提供环境。没有本类字段的派生定义无需保存新的字段环境。
继承字段总是使用声明它的祖先定义和环境。注解为本类名时匹配该实际定义,REPL 重定义同名类不会改变旧实例的自引用字段约束;其他名称在每次构造/写入时从声明环境动态解析真实 Type/Trait,前向名称可在构造前建立,未知名称写入报错。不会仅以字符串相同判断名义兼容。基本注解与函数检查共用匹配规则。字段环境尚不解析函数局部类型名称或捕获的类型变量。
原生共享引用对象以当前 type_target 决定声明,以 attribute_target 决定存储;共享替换后的别名因而按新内容的字段契约写入。宿主直接写裸存储对象、改载荷、附加类型或改变字段所引用对象的共享转发,不属于 `set_attr` 的完整初始化/持续不变量保证;这些低层入口仍由宿主维护。没有静态推断、字段默认值、非空递归图初始化语法、重写方差或效果系统。
回归源码位于 compiler/tests/classes.rs,覆盖普通/原生构造、继承、错误写入的值/版本保持、未知名称、动态类型重绑定、自引用定义、开关、删除和 GC 清理,仅编译未本地执行。Tessera 在真实原生程序中验证共享字段更新、错误赋值与构造前副作用保护。