feat: inherit native allocators and forward managed field storage
This commit is contained in:
@@ -51,6 +51,12 @@ 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 {
|
||||
/// Ordinary field storage may be indirect, independently of type identity.
|
||||
/// Targets must be traced; retargeting records Contents on the forwarder.
|
||||
fn attribute_target(&self, _store: &crate::ObjectStore) -> Result<Option<ObjectRef>, VmError> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
||||
@@ -110,6 +110,24 @@ impl ObjectStore {
|
||||
self.heap.get(reference)
|
||||
}
|
||||
|
||||
/// Borrowed storage path: depend on Contents for intermediate objects and
|
||||
/// Attribute(name) on the final storage object. Every handle is validated.
|
||||
pub fn attribute_path(&self, reference: ObjectRef) -> Result<Vec<ObjectRef>, VmError> {
|
||||
let mut path = Vec::new();
|
||||
let mut seen = std::collections::BTreeSet::new();
|
||||
let mut current = reference;
|
||||
loop {
|
||||
if !seen.insert(current) {
|
||||
return Err(VmError::InvalidArgument("cyclic attribute storage"));
|
||||
}
|
||||
path.push(current);
|
||||
match self.object(current)?.payload().attribute_target(self)? {
|
||||
Some(target) => current = target,
|
||||
None => return Ok(path),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn retain(&mut self, reference: ObjectRef) -> Result<(), VmError> {
|
||||
self.heap.retain(reference)
|
||||
}
|
||||
@@ -187,14 +205,22 @@ impl ObjectStore {
|
||||
if name == "type" && self.type_of(reference)?.is_some() {
|
||||
return Err(VmError::UnsupportedOperation("write object type"));
|
||||
}
|
||||
self.heap.set_attr(reference, name, value)
|
||||
let storage = *self
|
||||
.attribute_path(reference)?
|
||||
.last()
|
||||
.expect("nonempty attribute path");
|
||||
self.heap.set_attr(storage, 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)
|
||||
let storage = *self
|
||||
.attribute_path(reference)?
|
||||
.last()
|
||||
.expect("nonempty attribute path");
|
||||
self.heap.del_attr(storage, name)
|
||||
}
|
||||
|
||||
/// Validate a borrowed object handle without taking an owned reference.
|
||||
|
||||
+35
-10
@@ -12,6 +12,9 @@ pub enum TypeConstructor {
|
||||
Record,
|
||||
/// Delegate to a managed callable. It must return an instance of this type.
|
||||
Native(ObjectRef),
|
||||
/// Opt-in allocator inheritance. The requested nominal type is prepended
|
||||
/// to the constructor arguments; the allocator must create that real type.
|
||||
NativeTyped(ObjectRef),
|
||||
Abstract,
|
||||
}
|
||||
|
||||
@@ -41,6 +44,9 @@ impl EndorTypeObject {
|
||||
pub fn base(&self) -> Option<ObjectRef> {
|
||||
self.base
|
||||
}
|
||||
pub fn fields(&self) -> &[ClassField] {
|
||||
&self.fields
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectPayload for EndorTypeObject {
|
||||
@@ -54,7 +60,9 @@ impl ObjectPayload for EndorTypeObject {
|
||||
if let Some(base) = self.base {
|
||||
visit(base);
|
||||
}
|
||||
if let TypeConstructor::Native(function) = self.constructor {
|
||||
if let TypeConstructor::Native(function) | TypeConstructor::NativeTyped(function) =
|
||||
self.constructor
|
||||
{
|
||||
visit(function);
|
||||
}
|
||||
for method in self.methods.values() {
|
||||
@@ -84,6 +92,15 @@ impl NativeCallable for Construct {
|
||||
function,
|
||||
arguments: arguments.to_vec(),
|
||||
}),
|
||||
TypeConstructor::NativeTyped(function) => {
|
||||
let mut values = Vec::with_capacity(arguments.len() + 1);
|
||||
values.push(Value::Object(type_object));
|
||||
values.extend_from_slice(arguments);
|
||||
Ok(NativeResult::Invoke {
|
||||
function,
|
||||
arguments: values,
|
||||
})
|
||||
}
|
||||
TypeConstructor::Abstract => {
|
||||
Err(VmError::UnsupportedOperation("abstract type construction"))
|
||||
}
|
||||
@@ -157,7 +174,10 @@ impl ObjectStore {
|
||||
base: ObjectRef,
|
||||
constructor: TypeConstructor,
|
||||
) -> Result<ObjectRef, VmError> {
|
||||
self.payload::<EndorTypeObject>(base, "Type")?;
|
||||
let fields = self
|
||||
.payload::<EndorTypeObject>(base, "Type")?
|
||||
.fields
|
||||
.clone();
|
||||
let metatype = self
|
||||
.type_of(base)?
|
||||
.ok_or(VmError::InvalidArgument("base type has no metatype"))?;
|
||||
@@ -170,7 +190,7 @@ impl ObjectStore {
|
||||
base: Some(base),
|
||||
constructor,
|
||||
methods: BTreeMap::new(),
|
||||
fields: Vec::new(),
|
||||
fields,
|
||||
})?;
|
||||
if let Err(error) = self.attach_type(definition, metatype) {
|
||||
self.release(definition)?;
|
||||
@@ -258,8 +278,8 @@ impl ObjectStore {
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// Only explicitly inheritable native allocators are reused. Abstract
|
||||
/// subclasses remain abstract; no Record can forge a native payload.
|
||||
pub fn new_class(
|
||||
&mut self,
|
||||
name: impl Into<String>,
|
||||
@@ -268,11 +288,12 @@ impl ObjectStore {
|
||||
methods: BTreeMap<String, ObjectRef>,
|
||||
) -> Result<ObjectRef, VmError> {
|
||||
let definition = self.payload::<EndorTypeObject>(base, "Type")?;
|
||||
if !matches!(definition.constructor, TypeConstructor::Record) {
|
||||
if matches!(definition.constructor, TypeConstructor::Native(_)) {
|
||||
return Err(VmError::UnsupportedOperation(
|
||||
"source subclass of native or abstract allocator",
|
||||
"native allocator does not opt into source subclass construction",
|
||||
));
|
||||
}
|
||||
let constructor = definition.constructor;
|
||||
let mut fields = definition.fields.clone();
|
||||
for field in own_fields {
|
||||
if field.name.is_empty()
|
||||
@@ -304,7 +325,7 @@ impl ObjectStore {
|
||||
let class = self.allocate(EndorTypeObject {
|
||||
name,
|
||||
base: Some(base),
|
||||
constructor: TypeConstructor::Record,
|
||||
constructor,
|
||||
methods,
|
||||
fields,
|
||||
})?;
|
||||
@@ -406,7 +427,11 @@ impl ObjectStore {
|
||||
return Ok(AttributeLookup::Value(Value::Object(function)));
|
||||
}
|
||||
}
|
||||
match self.heap.get_attr(receiver, name) {
|
||||
let storage = *self
|
||||
.attribute_path(receiver)?
|
||||
.last()
|
||||
.expect("nonempty attribute path");
|
||||
match self.heap.get_attr(storage, name) {
|
||||
Ok(value) => return Ok(AttributeLookup::Value(value)),
|
||||
Err(VmError::AttributeNotFound(_) | VmError::UnsupportedOperation("attributes")) => {}
|
||||
Err(error) => return Err(error),
|
||||
@@ -418,7 +443,7 @@ impl ObjectStore {
|
||||
return Err(VmError::AttributeNotFound(name.into()));
|
||||
}
|
||||
self.heap
|
||||
.get_attr(receiver, name)
|
||||
.get_attr(storage, name)
|
||||
.map(AttributeLookup::Value)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user