Validate declared class fields at construction and attribute writes
This commit is contained in:
@@ -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 的操作数。
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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、结果根转移与失败清理 |
|
||||
|
||||
## 常见修改入口
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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、可选源码签名、共享不可变代码和操作数/控制流/栈验证 |
|
||||
|
||||
@@ -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
@@ -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
@@ -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 |
|
||||
|
||||
@@ -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 的共享引用章节。
|
||||
|
||||
## 工作约定
|
||||
|
||||
|
||||
@@ -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、异常数据 |
|
||||
|
||||
@@ -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
@@ -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 在真实原生程序中验证共享字段更新、错误赋值与构造前副作用保护。
|
||||
|
||||
Reference in New Issue
Block a user