diff --git a/crates/core/src/README.md b/crates/core/src/README.md index 9c195d2..90197dc 100644 --- a/crates/core/src/README.md +++ b/crates/core/src/README.md @@ -7,7 +7,7 @@ | [lib.rs](lib.rs) | VM 类型导出、objects 类型兼容重导出(含 ReadAccess/CallSignature/CallType/CallEffect)、宿主调用与字节码 rustdoc 示例 | | [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、结果根转移与失败清理 | ## 常见修改入口 diff --git a/crates/core/src/eval.rs b/crates/core/src/eval.rs index 3919782..8ee6953 100644 --- a/crates/core/src/eval.rs +++ b/crates/core/src/eval.rs @@ -166,14 +166,29 @@ impl Vm { for value in arguments { self.validate_value(value)?; } + let receiver = arguments + .first() + .or_else(|| { + if required == 0 { + data.defaults().first() + } else { + None + } + }) + .and_then(|value| match value { + Value::Object(receiver) => Some(*receiver), + _ => None, + }); + if self.source_type_checks + && let Some(receiver) = receiver + { + self.objects + .check_method_override_call(function, receiver)?; + } let mut return_type = None; if self.source_type_checks && let Some(signature) = executable.source_signature() { - let receiver = arguments.first().and_then(|value| match value { - Value::Object(object) => Some(*object), - _ => None, - }); for (index, expected_name) in signature.parameters.iter().enumerate() { let value = arguments .get(index) @@ -262,6 +277,12 @@ impl Vm { for argument in arguments { self.validate_value(argument)?; } + if self.source_type_checks + && let Some(Value::Object(receiver)) = arguments.first() + { + self.objects + .check_method_override_call(function, *receiver)?; + } if let Some(arity) = self.objects.call_arity(function)? && !arity.accepts(arguments.len()) { diff --git a/crates/core/tests/README.md b/crates/core/tests/README.md index 082cd70..bf86db2 100644 --- a/crates/core/tests/README.md +++ b/crates/core/tests/README.md @@ -8,6 +8,7 @@ | [builtins.rs](builtins.rs) | 无损 Number、Unicode 字符串、Dict 顺序与键、Cell、函数环境/共享代码、异常数据 | | [execution.rs](execution.rs) | wordcode 与校验、控制流、参数/默认值、嵌套帧、字节码函数创建/默认值/环境捕获、闭包、返回值存活、失败清理、限额 | | [types.rs](types.rs) | 名义身份、继承与实例关系、类型边保活/循环回收、只读类型元数据与外来句柄、显式Trait身份/继承/参数约束及后代更新原子性;统一函数分类、原生数量元数据/副作用前拒绝/能力更新/默认参数与返回类型错误根清理;仅远端执行 | +| [method_contracts.rs](method_contracts.rs) | 源码/原生方法覆写参数逆变/返回协变、联合参数、效果、祖先替换原子性、动态名称重绑定/前向名称与体前拒绝/恢复及清理;仅远端执行 | | [native_context.rs](native_context.rs) | 原生作用域根、分配失败清理、原生签名/联合与可空类型/效果和委托返回根、绑定方法元数据、委托、只读结果分配/写入拒绝/依赖/校验与普通读取区分及失败恢复、弱引用过期及有界变更;仅远端执行 | 核心对象现由 objects 定义,本目录保留既有 endor_core 导入与 Vm 方法的兼容性检查。独立对象存储检查见 [objects/tests](../../objects/tests/README.md)。堆内部伪造句柄与计数溢出检查在 objects 的 src/heap.rs,公开示例由 src/lib.rs rustdoc 提供。文件职责见 [src](../src/README.md),检查方式和结果边界见 [验证](../../../docs/operations/validation.md)。本地不执行测试;存在测试文件不代表某次提交已通过。返回 [core](../README.md)。 diff --git a/crates/core/tests/method_contracts.rs b/crates/core/tests/method_contracts.rs new file mode 100644 index 0000000..82fc04d --- /dev/null +++ b/crates/core/tests/method_contracts.rs @@ -0,0 +1,445 @@ +//! Compile here; execute only in the designated remote validation environment. +use endor_core::{ + AnnotationType as T, CallEffect, CallSignature, CallType, CodeLayout, ContextNativeFunction, + EndorCode, Instruction, NativeCallable, NativeContext, NativeResult, ObjectRef, Opcode, + SourceSignature, TypeConstructor, Value, Vm, VmError, +}; +use std::collections::BTreeMap; + +fn source( + vm: &mut Vm, + globals: ObjectRef, + owner: &str, + parameters: &[&str], + returns: &str, + result: Value, +) -> ObjectRef { + let code = EndorCode::from_instructions( + &[ + Instruction::new(Opcode::LoadConst, 0), + Instruction::new(Opcode::StoreGlobal, 0), + Instruction::new(Opcode::LoadConst, 1), + Instruction::simple(Opcode::ReturnValue), + ], + vec![Value::Number(42.into()), result], + CodeLayout { + parameters: (0..parameters.len()).map(|i| format!("p{i}")).collect(), + names: vec!["ran".into()], + ..CodeLayout::default() + }, + ) + .unwrap() + .with_source_signature(SourceSignature { + parameters: parameters.iter().map(|s| (*s).into()).collect(), + return_type: returns.into(), + 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 +} +fn class( + vm: &mut Vm, + name: &str, + base: ObjectRef, + function: ObjectRef, +) -> Result { + vm.new_class( + name, + base, + vec![], + BTreeMap::from([("check".into(), function)]), + ) +} + +#[test] +fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation() { + 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 base_fn = source( + &mut vm, + globals, + "Base", + &["Base", "Number"], + "Object", + Value::Nil, + ); + let base = class(&mut vm, "Base", roots.object, base_fn).unwrap(); + let good_fn = source( + &mut vm, + globals, + "Good", + &["Good", "Object"], + "Number", + Value::Number(7.into()), + ); + let good = class(&mut vm, "Good", base, good_fn).unwrap(); + let narrow = source( + &mut vm, + globals, + "Narrow", + &["Narrow", "String"], + "Number", + Value::Nil, + ); + assert!(matches!( + class(&mut vm, "Narrow", base, narrow), + Err(VmError::OverrideViolation { + reason: "override narrows a parameter type", + .. + }) + )); + let numeric = source( + &mut vm, + globals, + "Numeric", + &["Numeric", "Object"], + "Number", + Value::Number(0.into()), + ); + let numeric_type = class(&mut vm, "Numeric", roots.object, numeric).unwrap(); + let broad = source( + &mut vm, + globals, + "Broad", + &["Broad", "Object"], + "Object", + Value::Nil, + ); + assert!(matches!( + class(&mut vm, "Broad", numeric_type, broad), + Err(VmError::OverrideViolation { + reason: "override widens the return type", + .. + }) + )); + for object in [ + good, + good_fn, + narrow, + broad, + numeric_type, + numeric, + base, + base_fn, + globals, + roots.object, + roots.type_type, + ] { + vm.release(object).unwrap(); + } + vm.collect_cycles(); + assert_eq!(vm.object_count(), 0); +} + +#[test] +fn replacing_an_ancestor_validates_overriding_descendants_before_mutation() { + 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 base_fn = source( + &mut vm, + globals, + "Base", + &["Base", "Number"], + "Object", + Value::Nil, + ); + let base = class(&mut vm, "Base", roots.object, base_fn).unwrap(); + let child_fn = source( + &mut vm, + globals, + "Child", + &["Child", "Number"], + "Number", + Value::Number(0.into()), + ); + let child = class(&mut vm, "Child", base, child_fn).unwrap(); + let replacement = source( + &mut vm, + globals, + "Base", + &["Base", "Object"], + "Object", + Value::Nil, + ); + let version = vm.change_version(base).unwrap(); + assert!(matches!( + vm.define_method(base, "check", replacement), + Err(VmError::OverrideViolation { .. }) + )); + assert_eq!(vm.change_version(base).unwrap(), version); + assert_eq!( + vm.object_store().lookup_method(base, "check").unwrap(), + Some(base_fn) + ); + assert_eq!( + vm.object_store().lookup_method(child, "check").unwrap(), + Some(child_fn) + ); + for object in [ + replacement, + child, + child_fn, + base, + base_fn, + globals, + roots.object, + roots.type_type, + ] { + 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() { + let mut vm = Vm::new().unwrap(); + let roots = vm.bootstrap_types().unwrap(); + vm.set_source_type_checks(true).unwrap(); + let parent = vm + .new_type("Result", roots.object, TypeConstructor::Record) + .unwrap(); + let result_type = vm + .new_type("ResultChild", parent, TypeConstructor::Record) + .unwrap(); + let other = vm + .new_type("Other", roots.object, TypeConstructor::Record) + .unwrap(); + let result = vm.new_object().unwrap(); + vm.attach_type(result, result_type).unwrap(); + let base_globals = vm.new_dict().unwrap(); + let globals = vm.new_dict().unwrap(); + vm.dict_set( + base_globals, + Value::Text("Result".into()), + Value::Object(parent), + ) + .unwrap(); + vm.dict_set( + globals, + Value::Text("Actual".into()), + Value::Object(result_type), + ) + .unwrap(); + vm.dict_set(globals, Value::Text("ran".into()), Value::Number(0.into())) + .unwrap(); + let base_fn = source( + &mut vm, + base_globals, + "Base", + &["Base"], + "Result", + Value::Nil, + ); + let base = class(&mut vm, "Base", roots.object, base_fn).unwrap(); + let child_fn = source( + &mut vm, + globals, + "Child", + &["Child"], + "Actual", + Value::Object(result), + ); + let child = class(&mut vm, "Child", base, child_fn).unwrap(); + let receiver = vm.new_object().unwrap(); + vm.attach_type(receiver, child).unwrap(); + vm.dict_set(globals, Value::Text("Actual".into()), Value::Object(other)) + .unwrap(); + assert!(matches!( + vm.call(child_fn, &[Value::Object(receiver)]), + Err(VmError::OverrideViolation { .. }) + )); + assert_eq!( + vm.dict_get(globals, &Value::Text("ran".into())).unwrap(), + Some(Value::Number(0.into())) + ); + vm.dict_set( + globals, + Value::Text("Actual".into()), + 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(); + for object in [ + receiver, + child, + child_fn, + base, + base_fn, + globals, + base_globals, + result, + other, + result_type, + parent, + roots.object, + roots.type_type, + ] { + vm.release(object).unwrap(); + } + vm.collect_cycles(); + assert_eq!(vm.object_count(), 0); +} + +#[derive(Debug)] +struct Native(CallSignature); +impl NativeCallable for Native { + fn signature(&self) -> Option { + Some(self.0.clone()) + } + fn invoke(&self, _: &mut NativeContext<'_>, _: &[Value]) -> Result { + Ok(NativeResult::Return(Value::Number(0.into()))) + } +} +#[test] +fn native_overrides_preserve_union_inputs_and_do_not_expand_effects() { + let mut vm = Vm::new().unwrap(); + let roots = vm.bootstrap_types().unwrap(); + vm.set_source_type_checks(true).unwrap(); + let base = vm + .new_type("Base", roots.object, TypeConstructor::Record) + .unwrap(); + 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]); + let base_fn = vm + .allocate(ContextNativeFunction::new( + "check", + Native(signature), + vec![], + )) + .unwrap(); + vm.define_method(base, "check", base_fn).unwrap(); + let child = vm.new_type("Child", base, TypeConstructor::Record).unwrap(); + let effect_fn = vm + .allocate(ContextNativeFunction::new( + "effect", + Native(CallSignature::exact( + vec![T::Nominal(child), T::Any], + T::Number, + CallEffect::Mutating, + )), + vec![], + )) + .unwrap(); + assert!(matches!( + vm.define_method(child, "check", effect_fn), + Err(VmError::OverrideViolation { + reason: "override expands native effects", + .. + }) + )); + let narrow_fn = vm + .allocate(ContextNativeFunction::new( + "narrow", + Native(CallSignature::exact( + vec![T::Nominal(child), T::Number], + T::Number, + CallEffect::ReadOnly, + )), + vec![], + )) + .unwrap(); + assert!(matches!( + vm.define_method(child, "check", narrow_fn), + Err(VmError::OverrideViolation { + reason: "override narrows a parameter type", + .. + }) + )); + for object in [ + narrow_fn, + effect_fn, + child, + base_fn, + base, + roots.object, + roots.type_type, + ] { + vm.release(object).unwrap(); + } + vm.collect_cycles(); + assert_eq!(vm.object_count(), 0); +} + +#[test] +fn unknown_forward_names_defer_until_call_without_entering_the_body() { + 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(); + let base_fn = source( + &mut vm, + globals, + "Base", + &["Base", "Number"], + "Number", + Value::Number(0.into()), + ); + let base = class(&mut vm, "Base", roots.object, base_fn).unwrap(); + let child_fn = source( + &mut vm, + globals, + "Child", + &["Child", "Later"], + "Number", + Value::Number(0.into()), + ); + let child = class(&mut vm, "Child", base, child_fn).unwrap(); + let receiver = vm.new_object().unwrap(); + vm.attach_type(receiver, child).unwrap(); + assert!(matches!( + vm.call( + child_fn, + &[Value::Object(receiver), Value::Number(1.into())] + ), + Err(VmError::UnknownAnnotation(_)) + )); + let later = vm + .new_type("Later", roots.object, TypeConstructor::Record) + .unwrap(); + vm.dict_set(globals, Value::Text("Later".into()), Value::Object(later)) + .unwrap(); + assert!(matches!( + vm.call( + child_fn, + &[Value::Object(receiver), Value::Number(1.into())] + ), + Err(VmError::OverrideViolation { .. }) + )); + assert_eq!( + vm.dict_get(globals, &Value::Text("ran".into())).unwrap(), + Some(Value::Number(0.into())) + ); + for object in [ + receiver, + child, + child_fn, + base, + base_fn, + later, + globals, + roots.object, + roots.type_type, + ] { + vm.release(object).unwrap(); + } + vm.collect_cycles(); + assert_eq!(vm.object_count(), 0); +} diff --git a/crates/objects/src/README.md b/crates/objects/src/README.md index 0424a4d..5348ea7 100644 --- a/crates/objects/src/README.md +++ b/crates/objects/src/README.md @@ -7,6 +7,7 @@ | [lib.rs](lib.rs) | 包边界、对象类型/ObjectStore 公开导出(含Trait/MethodRequirement/SourceSignature/AnnotationType/CallArity/CallSignature/CallType/CallEffect/ReadAccess)与独立存储 rustdoc 示例 | | [object.rs](object.rs) | Value、CompareOp、ObjectPayload(含显式函数标记)、ObjectHeader、EndorObject、Record、可发布数量元数据的 NativeFunction | | [types.rs](types.rs) | 托管名义类型、显式 Object/Type 引导、继承查询、受管方法表/接收者绑定、类字段、显式Trait定义/声明/方法参数约束、原生构造委托、统一源码/原生/绑定方法参数区间查询与能力验证、声明环境托管边、字段构造/写入检查与标记为校验读取的注解解析/匹配 | +| [contracts.rs](contracts.rs) | 方法参数逆变/返回协变、名义/能力/联合包含关系、祖先方法更新的后代检查、动态注解调用前复查 | | [native.rs](native.rs) | NativeContext、CallArity/CallSignature 类型与效果元数据、调用前检查、签名类型强边、上下文原生函数、作用域语言函数工厂、显式能力声明入口、函数标记、根与非递归委托调用 | | [changes.rs](changes.rs) | 合并变更批次、变更键与有界通知额度 | | [number.rs](number.rs) | Integer/Decimal/Rational/BinaryFloat/Complex 的无损表示和装箱载荷 | @@ -16,7 +17,7 @@ | [store_builtins.rs](store_builtins.rs) | 载荷校验、代码/函数工厂、Dict/Cell 操作及键语义 | | [instruction.rs](instruction.rs) | Opcode、Instruction、wordcode 编解码、MakeFunction/比较参数与跳转读取接口 | | [code.rs](code.rs) | CodeLayout、EndorCode/EndorCodeObject、可选源码签名、共享不可变代码和操作数/控制流/栈验证 | -| [error.rs](error.rs) | 对象、代码与执行共用的 VmError(含TraitViolation、UnknownAnnotation、TypeMismatch),避免反向依赖 core | +| [error.rs](error.rs) | 对象、代码与执行共用的 VmError(含TraitViolation、OverrideViolation、UnknownAnnotation、TypeMismatch),避免反向依赖 core | 对象操作变更同步 [对象模型](../../../docs/runtime/objects.md),内置载荷同步 [基本对象](../../../docs/runtime/builtins.md)。新增操作码需要同时修改本包 instruction.rs/code.rs 和 core 的 eval.rs 分派,见 [字节码 VM](../../../docs/runtime/bytecode.md)。 diff --git a/crates/objects/src/contracts.rs b/crates/objects/src/contracts.rs new file mode 100644 index 0000000..371b93c --- /dev/null +++ b/crates/objects/src/contracts.rs @@ -0,0 +1,292 @@ +//! Method substitution rules. This is a child of types so validation can inspect +//! method tables without exporting mutable definition internals. +use super::{AnnotationType, EndorTypeObject}; +use crate::{ + CallArity, CallEffect, CallSignature, CallType, EndorCodeObject, EndorFunctionObject, + ObjectRef, ObjectStore, VmError, +}; + +struct Contract { + arity: CallArity, + parameters: Vec, + variadic: Option, + returns: CallType, + effect: Option, +} +impl Contract { + fn parameter(&self, index: usize) -> Option<&CallType> { + self.parameters.get(index).or(self.variadic.as_ref()) + } +} +impl From for Contract { + fn from(value: CallSignature) -> Self { + Self { + arity: value.arity, + parameters: value.parameters, + variadic: value.variadic, + returns: value.returns, + effect: Some(value.effect), + } + } +} +#[derive(Clone, Copy)] +struct Patch<'a> { + owner: ObjectRef, + name: &'a str, + function: ObjectRef, +} + +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. + pub fn accepts_type(&self, store: &ObjectStore, provided: &Self) -> Result { + self.validate(store)?; + provided.validate(store)?; + for actual in provided.alternatives() { + let mut accepted = false; + for expected in self.alternatives() { + accepted = match (*expected, *actual) { + (AnnotationType::Any, _) => true, + (AnnotationType::Nominal(base), AnnotationType::Nominal(child)) => { + store.is_subtype(child, base)? + } + ( + AnnotationType::Capability(capability), + AnnotationType::Nominal(definition), + ) => store.implements_trait(definition, capability)?, + (expected, actual) => expected == actual, + }; + if accepted { + break; + } + } + if !accepted { + return Ok(false); + } + } + Ok(true) + } +} + +impl ObjectStore { + fn method_contract( + &self, + function: ObjectRef, + owner: ObjectRef, + ) -> Result, VmError> { + if let Some(native) = self.native_signature(function)? { + return Ok(Some(native.into())); + } + let Some(data) = self + .object(function)? + .payload() + .as_any() + .downcast_ref::() + else { + return Ok(None); + }; + let code = self.payload::(data.code(), "code")?.code(); + let Some(signature) = code.source_signature() else { + return Ok(None); + }; + let resolve = |name: &str| { + if signature.method_owner.as_deref() == Some(name) { + let mut current = Some(owner); + while let Some(definition) = current { + let data = self.payload::(definition, "Type")?; + if data.name == name + && (definition == owner + || data + .methods + .values() + .any(|candidate| *candidate == function)) + { + return Ok(AnnotationType::Nominal(definition)); + } + current = data.base; + } + } + self.annotation_type(function, name, None) + }; + let parameters = signature + .parameters + .iter() + .map(|name| resolve(name).map(CallType::from)) + .collect::, _>>()?; + Ok(Some(Contract { + arity: self + .call_arity(function)? + .ok_or(VmError::InvalidArgument("source method lacks arity"))?, + parameters, + variadic: None, + returns: resolve(&signature.return_type)?.into(), + effect: None, + })) + } + fn own_method( + &self, + owner: ObjectRef, + name: &str, + patch: Option>, + ) -> Result, VmError> { + if let Some(patch) = patch + && patch.owner == owner + && patch.name == name + { + return Ok(Some(patch.function)); + } + Ok(self + .payload::(owner, "Type")? + .methods + .get(name) + .copied()) + } + 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))); + } + owner = self.payload::(definition, "Type")?.base; + } + Ok(None) + } + fn check_override( + &self, + owner: ObjectRef, + name: &str, + function: ObjectRef, + patch: Option>, + defer_unknown: bool, + ) -> Result<(), VmError> { + let definition = self.payload::(owner, "Type")?; + let Some((base_owner, base_function)) = + self.inherited_method(definition.base, name, patch)? + else { + return Ok(()); + }; + 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"))?; + 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"))? + .accepts_type(self, &receiver)? + { + return Err(invalid("override receiver rejects its declaring 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"))?; + 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"))?; + 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) { + let compatible = match expected { + CallEffect::ReadOnly => actual == CallEffect::ReadOnly, + CallEffect::Mutating => actual != CallEffect::External, + CallEffect::External => true, + }; + if !compatible { + return Err(invalid("override expands native effects")); + } + } + Ok(()) + })(); + match checked { + Err(VmError::UnknownAnnotation(_)) if defer_unknown => Ok(()), + result => result, + } + } + pub(super) fn validate_class_overrides(&self, definition: ObjectRef) -> Result<(), VmError> { + if self.field_type_checks { + for (name, function) in &self.payload::(definition, "Type")?.methods { + self.check_override(definition, name, *function, None, true)?; + } + } + Ok(()) + } + pub(super) fn validate_override_update( + &self, + owner: ObjectRef, + name: &str, + function: ObjectRef, + ) -> Result<(), VmError> { + if self.field_type_checks { + 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)?; + } + } + } + Ok(()) + } + /// Revalidate dynamic annotation names before a source method body executes. + /// Ordinary functions whose first argument is an object are not methods. + pub fn check_method_override_call( + &self, + function: ObjectRef, + receiver: ObjectRef, + ) -> Result<(), VmError> { + let mut owner = self.type_of(receiver)?; + while let Some(definition) = owner { + let data = self.payload::(definition, "Type")?; + for (name, candidate) in &data.methods { + if *candidate == function { + self.check_override(definition, name, function, None, false)?; + } + } + owner = data.base; + } + Ok(()) + } +} diff --git a/crates/objects/src/error.rs b/crates/objects/src/error.rs index 5b23d16..20eacd5 100644 --- a/crates/objects/src/error.rs +++ b/crates/objects/src/error.rs @@ -18,6 +18,11 @@ pub enum VmError { method: String, reason: &'static str, }, + OverrideViolation { + definition: String, + method: String, + reason: &'static str, + }, UnknownAnnotation(String), TypeMismatch { function: String, @@ -63,6 +68,11 @@ impl fmt::Display for VmError { Self::UnknownAnnotation(name) => { write!(f, "unknown or invalid type annotation: {name}") } + Self::OverrideViolation { + definition, + method, + reason, + } => write!(f, "override {definition}.{method}: {reason}"), Self::TypeMismatch { function, position, diff --git a/crates/objects/src/native.rs b/crates/objects/src/native.rs index 0658b37..9a8202f 100644 --- a/crates/objects/src/native.rs +++ b/crates/objects/src/native.rs @@ -75,7 +75,7 @@ impl CallType { pub fn nullable(value: crate::AnnotationType) -> Self { Self::Union(vec![value, crate::AnnotationType::Unit]) } - fn alternatives(&self) -> &[crate::AnnotationType] { + pub(crate) fn alternatives(&self) -> &[crate::AnnotationType] { match self { Self::Single(value) => std::slice::from_ref(value), Self::Union(values) => values, diff --git a/crates/objects/src/store.rs b/crates/objects/src/store.rs index 96a6fc0..08fe77f 100644 --- a/crates/objects/src/store.rs +++ b/crates/objects/src/store.rs @@ -15,7 +15,7 @@ pub struct ObjectStore { } impl ObjectStore { - /// Opt-in declared-field checks, shared by VM and NativeContext writes. + /// Opt-in declared-field and method substitution checks, shared with the VM. pub fn set_field_type_checks(&mut self, enabled: bool) -> Result<(), VmError> { if self.is_read_only() { return Err(VmError::UnsupportedOperation( diff --git a/crates/objects/src/types.rs b/crates/objects/src/types.rs index f1e41ef..12f6204 100644 --- a/crates/objects/src/types.rs +++ b/crates/objects/src/types.rs @@ -1,4 +1,6 @@ //! Nominal type identities in the same managed heap as their instances. +#[path = "contracts.rs"] +mod contracts; use std::{collections::BTreeMap, sync::Arc}; use crate::{ @@ -286,6 +288,7 @@ impl ObjectStore { )); } 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); let updated = EndorTypeObject { @@ -374,6 +377,7 @@ impl ObjectStore { })?; if let Err(error) = self .validate_inherited_traits(class) + .and_then(|()| self.validate_class_overrides(class)) .and_then(|()| self.attach_type(class, metatype)) { self.release(class)?; @@ -835,7 +839,7 @@ impl ObjectStore { /// Borrowed expected-type descriptor. Frames retain its managed reference /// while validating the return, so rebinding globals cannot release it mid-call. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AnnotationType { Any, Unit, diff --git a/docs/README.md b/docs/README.md index e770297..2610282 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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、contracts.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 | diff --git a/docs/runtime/README.md b/docs/runtime/README.md index 4f6225d..c35877b 100644 --- a/docs/runtime/README.md +++ b/docs/runtime/README.md @@ -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、异常数据 | diff --git a/docs/runtime/native-calls.md b/docs/runtime/native-calls.md index 8ceb762..eddaa9c 100644 --- a/docs/runtime/native-calls.md +++ b/docs/runtime/native-calls.md @@ -102,7 +102,7 @@ NativeResult::Invoke 仍是尾委托,不递归进入 Rust/VM。VM 为每层已 CallEffect::ReadOnly 声明只读现存对象,可分配临时结果;Mutating 与 External 在 ObjectStore 只读作用域内、进入实现前拒绝。普通执行允许这些效果。ReadOnly 声明不绕过已有写屏障,也不证明可信 Rust 不会进行隐藏 I/O;未知旧原生函数仍沿原调用/宿主检查,不能把 None 理解为纯函数。这不是静态效果推断、完整 I/O 沙箱或对阻塞原生代码的抢占。 -Tessera 的 measure/arrange/paint/hitTest、Point 几何查询、Window、Block/Container/Page、Mount、Task、Event、Subscription 方法和 ObjectList.at 发布签名。Page.open 的 Window | Unit、Page.window 的 Window | Unit 返回,以及 Block.on 的 Bool | String 可选阶段有准确元数据;Mount 的父/子/同级/Page 弱查询为可空结果。Mount.bounds 现在返回带 Bounds 名义类型的现有几何记录,可直接组合到 Point.inside 或视觉方法。滚动位置/字典等通用记录仍声明 Object,不冒充新语言类型;任务 owner 的具体资源资格仍按原域检查。构造器、其他管理 API 和完整源码覆写方差仍未全部接入。能力注册目前继续检查参数数量,不声称已经比较签名类型或效果。 +Tessera 的 measure/arrange/paint/hitTest、Point 几何查询、Window、Block/Container/Page、Mount、Task、Event、Subscription 方法和 ObjectList.at 发布签名。Page.open 的 Window | Unit、Page.window 的 Window | Unit 返回,以及 Block.on 的 Bool | String 可选阶段有准确元数据;Mount 的父/子/同级/Page 弱查询为可空结果。Mount.bounds 现在返回带 Bounds 名义类型的现有几何记录,可直接组合到 Point.inside 或视觉方法。滚动位置/字典等通用记录仍声明 Object,不冒充新语言类型;任务 owner 的具体资源资格仍按原域检查。构造器和其他管理 API 的签名覆盖仍未全部接入。已接入的方法覆写方差与动态名称规则见类型主题。能力注册目前继续检查参数数量,不声称已经比较签名类型或效果。 回归源码 native_context.rs 覆盖参数副作用前拒绝、错误结果清理、跨原生/源码委托检查、单帧限制、名义签名边与绑定方法循环回收、只读效果拒绝、传统直接函数及重复参数和元数据错误。只编译,不本地执行。 diff --git a/docs/runtime/types.md b/docs/runtime/types.md index 291bd0b..dc0c036 100644 --- a/docs/runtime/types.md +++ b/docs/runtime/types.md @@ -96,7 +96,7 @@ implement_trait(Type, Trait)明确建立能力关系并校验必需方法。只 | Type/Trait | 真实 Type/Trait 定义载荷 | | 其他名称 | 从函数自己的 globals、其次创建时捕获的 builtins 找到实际 Type 或 Trait;前者检查名义实例/继承,后者检查显式能力 | -普通名称每次调用重新解析,不是静态推断或定义时冻结的类型绑定。同名新定义不等于旧定义;自由函数可观察后续全局类型名重绑定。方法注解中指向自身声明类的名称,优先从实际接收者祖先中查找仍持有该函数的方法定义,因此 REPL 重定义全局类名后,原实例保存的绑定方法仍可调用。其他类型名仍遵循上述全局解析规则。此机制不提供重写方差、泛型、效果类型或原生参数/返回类型与效果签名;位置参数数量元数据见[原生调用](native-calls.md#统一位置参数数量)。字段赋值检查见下文。 +普通名称每次调用重新解析,不是静态推断或定义时冻结的类型绑定。同名新定义不等于旧定义;自由函数可观察后续全局类型名重绑定。方法注解中指向自身声明类的名称,优先从实际接收者祖先中查找仍持有该函数的方法定义,因此 REPL 重定义全局类名后,原实例保存的绑定方法仍可调用。其他类型名仍遵循上述全局解析规则。源码方法替换规则见下文;泛型和静态效果类型仍未实现,原生参数/返回类型与效果签名见原生调用主题;位置参数数量元数据见[原生调用](native-calls.md#统一位置参数数量)。字段赋值检查见下文。 源码与原生函数的统一名义分类由 `ObjectStore::set_function_type`/`Vm::set_function_type` 显式配置。配置验证 Type、拒绝只读修改,只保留弱引用且不可改成另一个定义;设置后新分配的显式 `ObjectPayload::is_function` 载荷自动附加该类型,已有对象不追溯改写。语言函数、两类原生函数及绑定方法提供此标记;仅有 call 能力不算函数。配置类型过期则函数分配报错并清理新对象,不引入隐藏 GC 根。宿主应在创建函数前配置,并通过正常对象边保活定义。 @@ -111,6 +111,21 @@ Record 构造在分配前校验全部位置字段。NativeTyped 分配器通过 继承字段总是使用声明它的祖先定义和环境。注解为本类名时匹配该实际定义,REPL 重定义同名类不会改变旧实例的自引用字段约束;其他名称在每次构造/写入时从声明环境动态解析真实 Type/Trait,前向名称可在构造前建立,未知名称写入报错。不会仅以字符串相同判断名义兼容。基本注解与函数检查共用匹配规则。字段环境尚不解析函数局部类型名称或捕获的类型变量。 -原生共享引用对象以当前 type_target 决定声明,以 attribute_target 决定存储;共享替换后的别名因而按新内容的字段契约写入。宿主直接写裸存储对象、改载荷、附加类型或改变字段所引用对象的共享转发,不属于 `set_attr` 的完整初始化/持续不变量保证;这些低层入口仍由宿主维护。没有静态推断、字段默认值、非空递归图初始化语法、重写方差或效果系统。 +原生共享引用对象以当前 type_target 决定声明,以 attribute_target 决定存储;共享替换后的别名因而按新内容的字段契约写入。宿主直接写裸存储对象、改载荷、附加类型或改变字段所引用对象的共享转发,不属于 `set_attr` 的完整初始化/持续不变量保证;这些低层入口仍由宿主维护。没有静态推断、字段默认值、非空递归图初始化语法或静态效果系统。 回归源码位于 compiler/tests/classes.rs,覆盖普通/原生构造、继承、错误写入的值/版本保持、未知名称、动态类型重绑定、自引用定义、开关、删除和 GC 清理,仅编译未本地执行。Tessera 在真实原生程序中验证共享字段更新、错误赋值与构造前副作用保护。 + + +## 方法覆写兼容 + +声明字段检查开关同时控制 new_class 与 define_method 的覆写替换验证;Vm.set_source_type_checks(true) 同时启用它。默认关闭以保留旧宿主行为。它与 Trait 的规范数量检查不同:对具有类型签名的实际基类方法,子方法必须接受基类允许的全部位置数量,参数类型只能扩大(逆变),返回类型只能缩小(协变)。接收者不按父类实例逆变,而必须接受子方法自己的声明类;self: Child 因而可以覆写 self: Base。普通未覆写的新方法仍按自身调用注解检查。 + +CallType::accepts_type 判断包含关系,支持 Object、基础值、实际名义继承、显式 Trait 实现及平坦联合;联合提供方的每一分支都必须被接收方至少一个分支覆盖。不同名义定义不以名称相同视作相等。不推断泛型、结构类型、函数内部可调用签名或关联类型。源码签名从真实参数/默认值和 SourceSignature 解析;自己的声明类绑定到实际定义,包括尚未发布的源码类和复用的祖先方法,不依赖重定义后的同名全局变量。 + +new_class 在发布之前校验本类覆写,失败释放候选定义。define_method 用候选替换进行只读检查,覆盖本类及所有已有后代的同名覆写,成功后才改变方法表;不能因后代遮蔽祖先的旧方法而跳过兼容验证。失败保持原方法、版本和继承关系。检查只借用定义/函数/注解身份,不引入强后代索引或冻结名称。 + +未知前向注解在类建立和方法更新时暂缓,其他已知不兼容以 OverrideViolation 报类名、方法名和原因。启用 VM 类型检查时,源码和原生调用均在进入实现前检查实际接收者所属方法的当前替换契约;UnknownAnnotation 此时不能暂缓。普通函数的首参数恰好是对象,不会因此被视为方法。默认接收者也使用实际默认参数值参与复查。每次复查动态全局注解,所以 REPL 重绑定使旧覆写不兼容时,函数体执行前拒绝;恢复兼容绑定后可继续普通调用。不回滚先前已发生的其他输入副作用。 + +已知原生效果只允许缩小:ReadOnly 基类只能被 ReadOnly 原生覆写;Mutating 基类不允许 External 覆写;External 可接受三者。源码效果尚无静态元数据,不能声称已经证明其纯度;实际只读布局/绘制仍通过原有堆屏障与原生效果入口限制执行。未知旧基类类型签名仍是未证明的兼容边界,Trait 当前仍仅校验数量,尚未提供独立的类型/效果要求。 + +回归源码 core/tests/method_contracts.rs 覆盖合法参数扩大/返回缩小、错误参数/返回、祖先替换对已有覆写后代的原子拒绝、调用前动态重绑定拒绝和恢复、前向名称、原生联合与效果及根清理,仅编译,不在本地执行。