feat: add managed types, source classes and scoped native calls

This commit is contained in:
2026-10-21 01:47:08 +08:00
parent 7c842789c0
commit a970fd7707
48 changed files with 2069 additions and 134 deletions
+3
View File
@@ -6,6 +6,9 @@
| --- | --- |
| [lib.rs](lib.rs) | 包边界、对象类型/ObjectStore 公开导出与独立存储 rustdoc 示例 |
| [object.rs](object.rs) | Value、CompareOp、ObjectPayload、ObjectHeader、EndorObject、Record、NativeFunction |
| [types.rs](types.rs) | 托管名义类型、显式 Object/Type 引导、继承查询、受管方法表/接收者绑定、类字段与原生构造委托 |
| [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、引用事务、迭代释放、循环回收及私有边界测试 |
+26
View File
@@ -0,0 +1,26 @@
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.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum ChangeKey {
Attribute(String),
Contents,
}
#[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.
#[derive(Debug, Default)]
pub struct ChangeBatch {
pub changes: Vec<ObjectChange>,
pub rescan_required: bool,
}
pub(crate) const MAX_PENDING_CHANGES: usize = 4096;
+1
View File
@@ -344,6 +344,7 @@ fn verify(
let consumed = defaults + closure + 1;
(consumed, consumed, 1)
}
Opcode::BuildClass => (4, 4, 1),
Opcode::ReturnValue => (1, 1, 0),
_ => (0, 0, 0),
};
+83 -1
View File
@@ -2,6 +2,8 @@ use std::collections::BTreeMap;
use std::fmt;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::changes::MAX_PENDING_CHANGES;
use crate::{ChangeBatch, ChangeKey, ObjectChange};
use crate::{EndorObject, ObjectHeader, ObjectPayload, Value, VmError};
static NEXT_HEAP_ID: AtomicU64 = AtomicU64::new(1);
@@ -21,6 +23,11 @@ impl fmt::Display for ObjectRef {
}
}
/// A non-owning reference. Only the originating store can upgrade it; slot
/// reuse never makes a dead weak reference point at a different object.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WeakObjectRef(pub(crate) ObjectRef);
#[derive(Debug)]
struct Slot {
generation: u64,
@@ -33,6 +40,8 @@ pub(crate) struct Heap {
slots: Vec<Slot>,
free: Vec<usize>,
live: usize,
changes: BTreeMap<(ObjectRef, ChangeKey), u64>,
changes_overflow: bool,
}
impl Heap {
@@ -45,6 +54,8 @@ impl Heap {
slots: Vec::new(),
free: Vec::new(),
live: 0,
changes: BTreeMap::new(),
changes_overflow: false,
})
}
@@ -72,6 +83,8 @@ impl Heap {
identity: reference,
strong_refs: 1,
external_refs: 1,
change_version: 0,
type_object: None,
},
payload,
attributes: BTreeMap::new(),
@@ -92,6 +105,27 @@ impl Heap {
.ok_or(VmError::InvalidHandle)
}
pub(crate) fn attach_type(
&mut self,
reference: ObjectRef,
type_object: ObjectRef,
) -> Result<(), VmError> {
if self.get(reference)?.header.type_object.is_some() {
return Err(VmError::InvalidArgument("object already has a 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);
self.get_mut(reference)?.header.type_object = Some(type_object);
self.record_change(reference, ChangeKey::Contents);
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.
@@ -104,10 +138,12 @@ impl Heap {
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);
self.release_edges(old_edges);
drop(old_payload);
Ok(())
@@ -194,8 +230,13 @@ impl Heap {
_ => 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, value);
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]);
}
@@ -213,17 +254,58 @@ impl Heap {
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()));
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)?
.header
.change_version
.checked_add(1)
.ok_or(VmError::ExecutionLimit("object change version"))?;
Ok(())
}
pub(crate) fn record_change(&mut self, reference: ObjectRef, key: ChangeKey) {
let object = self.get_mut(reference).expect("validated change target");
object.header.change_version += 1;
let version = object.header.change_version;
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);
}
}
pub(crate) fn take_changes(&mut self) -> ChangeBatch {
ChangeBatch {
changes: std::mem::take(&mut self.changes)
.into_iter()
.map(|((object, key), version)| ObjectChange {
object,
key,
version,
})
.collect(),
rescan_required: std::mem::take(&mut self.changes_overflow),
}
}
fn check_attributes(&self, reference: ObjectRef) -> Result<(), VmError> {
if !self.get(reference)?.payload.allows_attributes() {
return Err(VmError::UnsupportedOperation("attributes"));
+3
View File
@@ -34,6 +34,7 @@ pub enum Opcode {
StoreAttr = 26,
BuildMap = 27,
MakeFunction = 28,
BuildClass = 29,
ExtendedArg = 255,
}
@@ -69,6 +70,7 @@ impl Opcode {
26 => Self::StoreAttr,
27 => Self::BuildMap,
28 => Self::MakeFunction,
29 => Self::BuildClass,
255 => Self::ExtendedArg,
_ => return None,
})
@@ -83,6 +85,7 @@ impl Opcode {
| Self::ReturnValue
| Self::ToBool
| Self::UnaryNot
| Self::BuildClass
)
}
}
+7 -1
View File
@@ -14,23 +14,28 @@
//! ```
mod builtins;
mod changes;
mod code;
mod error;
mod heap;
mod instruction;
mod native;
mod number;
mod object;
mod store;
mod store_builtins;
mod types;
pub use builtins::{
EndorBaseExceptionObject, EndorCellObject, EndorDictObject, EndorFunctionObject,
EndorStringObject, EndorTracebackEntry,
};
pub use changes::{ChangeBatch, ChangeKey, ObjectChange};
pub use code::{CodeLayout, EndorCode, EndorCodeObject};
pub use error::VmError;
pub use heap::ObjectRef;
pub use heap::{ObjectRef, WeakObjectRef};
pub use instruction::{DecodedInstruction, Instruction, Opcode};
pub use native::{ContextNativeFunction, NativeCallable, NativeContext, NativeResult};
pub use number::{
EndorDecimal, EndorInteger, EndorNumber, EndorNumberObject, EndorRational, EndorReal,
};
@@ -39,3 +44,4 @@ pub use object::{
ObjectHeader, ObjectPayload, Record, Value,
};
pub use store::ObjectStore;
pub use types::{ClassField, EndorTypeObject, RootTypes, TypeConstructor};
+206
View File
@@ -0,0 +1,206 @@
use std::{fmt, sync::Arc};
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)]
pub enum NativeResult {
Return(Value),
Invoke {
function: ObjectRef,
arguments: Vec<Value>,
},
}
pub trait NativeCallable: fmt::Debug + Send + Sync + 'static {
fn invoke(
&self,
context: &mut NativeContext<'_>,
arguments: &[Value],
) -> Result<NativeResult, VmError>;
}
/// Native allocation/mutation scope. Allocations and upgraded weak references
/// 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<ObjectRef>,
callee: ObjectRef,
}
impl<'a> NativeContext<'a> {
/// Callable currently dispatched by the VM, borrowed for this scope.
pub fn callee(&self) -> ObjectRef {
self.callee
}
pub fn attach_type(
&mut self,
object: ObjectRef,
type_object: ObjectRef,
) -> Result<(), VmError> {
self.store.attach_type(object, type_object)
}
pub fn store(&self) -> &ObjectStore {
self.store
}
pub fn allocate(&mut self, payload: impl ObjectPayload) -> Result<ObjectRef, VmError> {
let object = self.store.allocate(payload)?;
self.roots.push(object);
Ok(object)
}
pub fn new_object(&mut self) -> Result<ObjectRef, VmError> {
self.allocate(crate::Record)
}
pub fn new_dict(&mut self) -> Result<ObjectRef, VmError> {
let object = self.store.new_dict()?;
self.roots.push(object);
Ok(object)
}
pub fn new_cell(&mut self, value: Option<Value>) -> Result<ObjectRef, VmError> {
let object = self.store.new_cell(value)?;
self.roots.push(object);
Ok(object)
}
pub fn set_attr(
&mut self,
object: ObjectRef,
name: impl Into<String>,
value: Value,
) -> Result<(), VmError> {
self.store.set_attr(object, name, value)
}
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)
}
pub fn dict_delete(&mut self, object: ObjectRef, key: &Value) -> Result<bool, VmError> {
self.store.dict_delete(object, key)
}
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,
payload: impl ObjectPayload,
) -> Result<(), VmError> {
self.store.replace_payload(object, payload)
}
pub fn upgrade(&mut self, weak: WeakObjectRef) -> Result<Option<ObjectRef>, VmError> {
let object = self.store.upgrade(weak)?;
if let Some(object) = object {
self.roots.push(object);
}
Ok(object)
}
fn protect(&mut self, value: &Value) -> Result<(), VmError> {
if let Value::Object(object) = value {
self.store.retain(*object)?;
self.roots.push(*object);
}
Ok(())
}
/// Internal execution bridge. Every returned object reference owns a root;
/// consumers must release Return values or all Invoke arguments/function.
pub fn execute(
store: &'a mut ObjectStore,
function: ObjectRef,
arguments: &[Value],
callable: Arc<dyn NativeCallable>,
) -> Result<NativeResult, VmError> {
let mut context = Self {
store,
roots: Vec::new(),
callee: function,
};
context.protect(&Value::Object(function))?;
for argument in arguments {
context.protect(argument)?;
}
let result = callable.invoke(&mut context, arguments)?;
let transferred_from = context.roots.len();
match &result {
NativeResult::Return(value) => context.protect(value)?,
NativeResult::Invoke {
function,
arguments,
} => {
context.protect(&Value::Object(*function))?;
for argument in arguments {
context.protect(argument)?;
}
}
}
// Transfer exactly the output roots; all other roots remain scoped.
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)
.expect("native scope owns its roots");
}
}
}
/// A context-aware function with explicit, immutable captured object edges.
#[derive(Debug)]
pub struct ContextNativeFunction {
name: &'static str,
callable: Arc<dyn NativeCallable>,
captures: Vec<ObjectRef>,
}
impl ContextNativeFunction {
pub fn new(
name: &'static str,
callable: impl NativeCallable,
captures: Vec<ObjectRef>,
) -> Self {
Self {
name,
callable: Arc::new(callable),
captures,
}
}
}
impl ObjectPayload for ContextNativeFunction {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn kind_name(&self) -> &'static str {
self.name
}
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
for capture in &self.captures {
visit(*capture);
}
}
fn native_callable(&self) -> Option<Arc<dyn NativeCallable>> {
Some(self.callable.clone())
}
}
+40 -2
View File
@@ -51,6 +51,19 @@ impl CompareOp {
/// Rust Drop releases host resources when the payload is destroyed.
/// Equal payloads must provide compatible hashes, or reject hashing explicitly.
pub trait ObjectPayload: Any + fmt::Debug + Send + Sync {
/// 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.
fn type_target(&self, _store: &crate::ObjectStore) -> Result<Option<ObjectRef>, VmError> {
Ok(None)
}
/// Optional context-aware native entry. The VM clones this dispatcher before
/// borrowing the store mutably; callbacks never borrow a live payload.
fn native_callable(&self) -> Option<std::sync::Arc<dyn crate::NativeCallable>> {
None
}
fn as_any(&self) -> &dyn Any;
/// A diagnostic label only, with no language type or inheritance semantics.
@@ -104,6 +117,18 @@ pub trait ObjectPayload: Any + fmt::Debug + Send + Sync {
fn assign_from(&self, _source: &dyn ObjectPayload) -> Result<bool, VmError> {
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.
fn assignment_cell(
&self,
_source: ObjectRef,
_store: &crate::ObjectStore,
) -> Result<Option<ObjectRef>, VmError> {
Ok(None)
}
}
#[derive(Debug, Default)]
@@ -166,15 +191,25 @@ impl ObjectPayload for EndorNativeFunctionObject {
}
}
/// Common runtime state. There is deliberately no language type pointer.
/// Common runtime state. Embedders may attach an immutable managed type.
#[derive(Debug)]
pub struct ObjectHeader {
pub(crate) identity: ObjectRef,
pub(crate) strong_refs: usize,
pub(crate) external_refs: usize,
pub(crate) change_version: u64,
pub(crate) type_object: Option<ObjectRef>,
}
impl ObjectHeader {
pub fn type_object(&self) -> Option<ObjectRef> {
self.type_object
}
pub fn change_version(&self) -> u64 {
self.change_version
}
pub fn identity(&self) -> ObjectRef {
self.identity
}
@@ -190,7 +225,7 @@ impl ObjectHeader {
}
/// Runtime object foundation, analogous in role to CPython's common object
/// structure, without its type system or binary layout. The heap boxes this
/// structure, without its binary layout. The heap boxes this
/// structure so its address remains stable throughout its lifetime.
#[derive(Debug)]
pub struct EndorObject {
@@ -213,6 +248,9 @@ impl EndorObject {
/// allocation and refreshed on VM-managed payload replacement; attributes
/// are maintained by the VM's reference accounting.
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);
}
+91 -3
View File
@@ -1,5 +1,8 @@
use crate::heap::Heap;
use crate::{CompareOp, EndorObject, ObjectPayload, ObjectRef, Record, Value, VmError};
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
@@ -10,6 +13,80 @@ pub struct ObjectStore {
}
impl ObjectStore {
pub fn downgrade(&self, reference: ObjectRef) -> Result<WeakObjectRef, VmError> {
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.
pub fn upgrade(&mut self, weak: WeakObjectRef) -> Result<Option<ObjectRef>, VmError> {
match self.object(weak.0) {
Ok(_) => {
self.retain(weak.0)?;
Ok(Some(weak.0))
}
Err(VmError::InvalidHandle) => Ok(None),
Err(error) => Err(error),
}
}
pub fn take_changes(&mut self) -> ChangeBatch {
self.heap.take_changes()
}
pub fn change_version(&self, reference: ObjectRef) -> Result<u64, VmError> {
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> {
if self
.object(reference)?
.payload()
.as_any()
.is::<crate::EndorTypeObject>()
{
return Err(VmError::UnsupportedOperation(
"replace immutable type definition",
));
}
self.validate_builtin(&payload)?;
self.heap.replace_payload(reference, payload)
}
pub fn assign_from(&mut self, target: ObjectRef, source: ObjectRef) -> Result<bool, VmError> {
self.heap.prepare_change(target)?;
self.object(source)?;
if let Some(cell) = self
.object(target)?
.payload()
.assignment_cell(source, self)?
{
// Replacing the Cell edge may otherwise reclaim a target which is
// reachable only through the edge being removed.
self.retain(target)?;
let result = self.cell_set(cell, Value::Object(source));
if result.is_ok() {
self.heap.record_change(target, ChangeKey::Contents);
}
self.release(target)?;
return result.map(|()| true);
}
let changed = self
.object(target)?
.payload()
.assign_from(self.object(source)?.payload())?;
if changed {
self.heap.record_change(target, ChangeKey::Contents);
}
Ok(changed)
}
pub fn new() -> Result<Self, VmError> {
Ok(Self { heap: Heap::new()? })
}
@@ -91,7 +168,11 @@ 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.
pub fn get_attr(&self, reference: ObjectRef, name: &str) -> Result<Value, VmError> {
self.heap.get_attr(reference, name)
self.lookup_attr(reference, name)
.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
@@ -102,10 +183,17 @@ impl ObjectStore {
name: impl Into<String>,
value: Value,
) -> Result<(), VmError> {
self.heap.set_attr(reference, name.into(), value)
let name = name.into();
if name == "type" && self.type_of(reference)?.is_some() {
return Err(VmError::UnsupportedOperation("write object type"));
}
self.heap.set_attr(reference, name, value)
}
pub fn del_attr(&mut self, reference: ObjectRef, name: &str) -> Result<(), VmError> {
if name == "type" && self.type_of(reference)?.is_some() {
return Err(VmError::UnsupportedOperation("delete object type"));
}
self.heap.del_attr(reference, name)
}
+474
View File
@@ -0,0 +1,474 @@
//! Nominal type identities in the same managed heap as their instances.
use std::{collections::BTreeMap, sync::Arc};
use crate::{
NativeCallable, NativeContext, NativeResult, ObjectPayload, ObjectRef, ObjectStore, Value,
VmError,
};
#[derive(Debug, Clone, Copy)]
pub enum TypeConstructor {
/// Allocate a plain, attribute-bearing instance of this type.
Record,
/// Delegate to a managed callable. It must return an instance of this type.
Native(ObjectRef),
Abstract,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClassField {
pub name: String,
/// Preserved source annotation, like current function annotations.
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.
#[derive(Debug)]
pub struct EndorTypeObject {
name: String,
base: Option<ObjectRef>,
constructor: TypeConstructor,
methods: BTreeMap<String, ObjectRef>,
fields: Vec<ClassField>,
}
impl EndorTypeObject {
pub fn name(&self) -> &str {
&self.name
}
pub fn base(&self) -> Option<ObjectRef> {
self.base
}
}
impl ObjectPayload for EndorTypeObject {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn kind_name(&self) -> &'static str {
"type"
}
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
if let Some(base) = self.base {
visit(base);
}
if let TypeConstructor::Native(function) = self.constructor {
visit(function);
}
for method in self.methods.values() {
visit(*method);
}
}
fn repr(&self, _: ObjectRef) -> Result<String, VmError> {
Ok(format!("<type {}>", self.name))
}
fn native_callable(&self) -> Option<Arc<dyn NativeCallable>> {
Some(Arc::new(Construct))
}
}
#[derive(Debug)]
struct Construct;
impl NativeCallable for Construct {
fn invoke(
&self,
cx: &mut NativeContext<'_>,
arguments: &[Value],
) -> Result<NativeResult, VmError> {
let type_object = cx.callee();
let definition = cx.store().payload::<EndorTypeObject>(type_object, "Type")?;
match definition.constructor {
TypeConstructor::Native(function) => Ok(NativeResult::Invoke {
function,
arguments: arguments.to_vec(),
}),
TypeConstructor::Abstract => {
Err(VmError::UnsupportedOperation("abstract type construction"))
}
TypeConstructor::Record => {
if arguments.len() != definition.fields.len() {
return Err(VmError::ArgumentCount {
name: definition.name.clone(),
required: definition.fields.len(),
maximum: definition.fields.len(),
provided: arguments.len(),
});
}
let fields = definition.fields.clone();
let instance = cx.new_object()?;
cx.attach_type(instance, type_object)?;
for (field, value) in fields.iter().zip(arguments) {
cx.set_attr(instance, &field.name, value.clone())?;
}
Ok(NativeResult::Return(Value::Object(instance)))
}
}
}
}
/// Each field owns one external root; the caller must release both.
#[derive(Debug, Clone, Copy)]
pub struct RootTypes {
pub object: ObjectRef,
pub type_type: ObjectRef,
}
impl ObjectStore {
/// Explicit bootstrap preserves the empty-store contract. Inheritance:
/// Type -> Object; instance relations: Object : Type and Type : Type.
pub fn bootstrap_types(&mut self) -> Result<RootTypes, VmError> {
let object = self.allocate(EndorTypeObject {
name: "Object".into(),
base: None,
constructor: TypeConstructor::Record,
methods: BTreeMap::new(),
fields: Vec::new(),
})?;
let mut allocated_type = None;
let result = (|| {
let type_type = self.allocate(EndorTypeObject {
name: "Type".into(),
base: Some(object),
constructor: TypeConstructor::Abstract,
methods: BTreeMap::new(),
fields: Vec::new(),
})?;
allocated_type = Some(type_type);
self.attach_type(object, type_type)?;
self.attach_type(type_type, type_type)?;
Ok(RootTypes { object, type_type })
})();
if result.is_err() {
if let Some(type_type) = allocated_type {
self.release(type_type)?;
}
self.release(object)?;
self.collect_cycles();
}
result
}
/// Base links are fixed at allocation, preventing inheritance cycles.
pub fn new_type(
&mut self,
name: impl Into<String>,
base: ObjectRef,
constructor: TypeConstructor,
) -> Result<ObjectRef, VmError> {
self.payload::<EndorTypeObject>(base, "Type")?;
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 definition = self.allocate(EndorTypeObject {
name,
base: Some(base),
constructor,
methods: BTreeMap::new(),
fields: Vec::new(),
})?;
if let Err(error) = self.attach_type(definition, metatype) {
self.release(definition)?;
return Err(error);
}
Ok(definition)
}
pub fn attach_type(
&mut self,
object: ObjectRef,
type_object: ObjectRef,
) -> Result<(), VmError> {
self.payload::<EndorTypeObject>(type_object, "Type")?;
self.heap.attach_type(object, type_object)
}
/// Borrowed type reference; legacy/untyped host objects return None.
pub fn type_of(&self, object: ObjectRef) -> Result<Option<ObjectRef>, VmError> {
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()),
}
}
}
pub fn is_subtype(&self, candidate: ObjectRef, expected: ObjectRef) -> Result<bool, VmError> {
self.payload::<EndorTypeObject>(expected, "Type")?;
let mut current = Some(candidate);
while let Some(type_object) = current {
let definition = self.payload::<EndorTypeObject>(type_object, "Type")?;
if type_object == expected {
return Ok(true);
}
current = definition.base;
}
Ok(false)
}
pub fn is_instance(&self, object: ObjectRef, expected: ObjectRef) -> Result<bool, VmError> {
self.payload::<EndorTypeObject>(expected, "Type")?;
match self.type_of(object)? {
Some(type_object) => self.is_subtype(type_object, expected),
None => Ok(false),
}
}
/// Updating the managed method table preserves nominal identity and base.
pub fn define_method(
&mut self,
type_object: ObjectRef,
name: impl Into<String>,
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::<EndorTypeObject>(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();
methods.insert(name, function);
let updated = EndorTypeObject {
name: definition.name.clone(),
base: definition.base,
constructor: definition.constructor,
methods,
fields: definition.fields.clone(),
};
self.heap.replace_payload(type_object, updated)
}
/// Atomically build a source class, including inherited constructor fields.
/// Native allocation needs its own protocol; never forge a Record payload
/// claiming to be a native visual instance.
pub fn new_class(
&mut self,
name: impl Into<String>,
base: ObjectRef,
own_fields: Vec<ClassField>,
methods: BTreeMap<String, ObjectRef>,
) -> Result<ObjectRef, VmError> {
let definition = self.payload::<EndorTypeObject>(base, "Type")?;
if !matches!(definition.constructor, TypeConstructor::Record) {
return Err(VmError::UnsupportedOperation(
"source subclass of native or abstract allocator",
));
}
let mut fields = definition.fields.clone();
for field in own_fields {
if field.name.is_empty()
|| field.name == "type"
|| fields.iter().any(|old| old.name == field.name)
|| self.lookup_method(base, &field.name)?.is_some()
{
return Err(VmError::InvalidArgument(
"duplicate or reserved class field",
));
}
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),
constructor: TypeConstructor::Record,
methods,
fields,
})?;
if let Err(error) = self.attach_type(class, metatype) {
self.release(class)?;
return Err(error);
}
Ok(class)
}
pub fn lookup_method(
&self,
type_object: ObjectRef,
name: &str,
) -> Result<Option<ObjectRef>, VmError> {
let mut current = Some(type_object);
while let Some(type_object) = current {
let definition = self.payload::<EndorTypeObject>(type_object, "Type")?;
if let Some(function) = definition.methods.get(name) {
return Ok(Some(*function));
}
current = definition.base;
}
Ok(None)
}
/// Source positional parameters after receiver binding. None means the
/// callable uses a native protocol without a source signature.
pub fn positional_parameter_count(
&self,
function: ObjectRef,
) -> Result<Option<usize>, VmError> {
let mut current = function;
let mut receivers = 0usize;
loop {
let payload = self.object(current)?.payload();
if let Some(bound) = payload.as_any().downcast_ref::<BoundMethod>() {
receivers = receivers
.checked_add(1)
.ok_or(VmError::ExecutionLimit("bound receivers"))?;
current = bound.function;
} else if let Some(function) = payload
.as_any()
.downcast_ref::<crate::EndorFunctionObject>()
{
let count = self
.payload::<crate::EndorCodeObject>(function.code(), "code")?
.code()
.parameters()
.len();
return count
.checked_sub(receivers)
.map(Some)
.ok_or(VmError::InvalidArgument(
"method receiver exceeds parameter count",
));
} else {
return Ok(None);
}
}
}
/// Returns one owned root, including for a newly bound inherited method.
pub fn bind_attr(&mut self, receiver: ObjectRef, name: &str) -> Result<Value, VmError> {
match self.lookup_attr(receiver, name)? {
AttributeLookup::Value(value) => {
if let Value::Object(object) = value {
self.retain(object)?;
}
Ok(value)
}
AttributeLookup::Method { function, receiver } => self
.allocate(BoundMethod { function, receiver })
.map(Value::Object),
}
}
pub(crate) fn lookup_attr(
&self,
receiver: ObjectRef,
name: &str,
) -> Result<AttributeLookup, VmError> {
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 let Some(definition) = object.payload().as_any().downcast_ref::<EndorTypeObject>() {
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 {
return Ok(AttributeLookup::Value(value));
}
if let Some(function) = self.lookup_method(receiver, name)? {
return Ok(AttributeLookup::Value(Value::Object(function)));
}
}
match self.heap.get_attr(receiver, name) {
Ok(value) => return Ok(AttributeLookup::Value(value)),
Err(VmError::AttributeNotFound(_) | VmError::UnsupportedOperation("attributes")) => {}
Err(error) => return Err(error),
}
if let Some(type_object) = self.type_of(receiver)? {
if let Some(function) = self.lookup_method(type_object, name)? {
return Ok(AttributeLookup::Method { function, receiver });
}
return Err(VmError::AttributeNotFound(name.into()));
}
self.heap
.get_attr(receiver, name)
.map(AttributeLookup::Value)
}
}
pub(crate) enum AttributeLookup {
Value(Value),
Method {
function: ObjectRef,
receiver: ObjectRef,
},
}
#[derive(Debug)]
struct BoundMethod {
receiver: ObjectRef,
function: ObjectRef,
}
impl ObjectPayload for BoundMethod {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn kind_name(&self) -> &'static str {
"bound-method"
}
fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) {
visit(self.receiver);
visit(self.function);
}
fn native_callable(&self) -> Option<Arc<dyn NativeCallable>> {
Some(Arc::new(CallBoundMethod))
}
}
#[derive(Debug)]
struct CallBoundMethod;
impl NativeCallable for CallBoundMethod {
fn invoke(
&self,
cx: &mut NativeContext<'_>,
arguments: &[Value],
) -> Result<NativeResult, VmError> {
let bound = cx
.store()
.payload::<BoundMethod>(cx.callee(), "bound method")?;
let mut values = Vec::with_capacity(arguments.len() + 1);
values.push(Value::Object(bound.receiver));
values.extend_from_slice(arguments);
Ok(NativeResult::Invoke {
function: bound.function,
arguments: values,
})
}
}