Implement vtables and method resolution

Types now can have vtable elements which are used by instances to bind
themselves to methods. When Op::GetAttr is executed, it calls a new
function, Obj::get_attr_lazy. This will search:

* attributes on the object
* vtable on the object's type
* vtable on the object's type's type,
* etc.

This searches up the type tree for a named value. If it exists as an
attribute, it will be returned immediately. If it exists in the type's
vtable, then it will be inserted as an attribute. If the vtable value is
a function, the object that it is being called on will be bound to that
method as the `self` parameter.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2024-09-23 20:59:00 -07:00
parent 8b931e9d12
commit 0d04090a99
2 changed files with 201 additions and 177 deletions

View File

@@ -1,5 +1,5 @@
// TODO obj.rs - remove the warning suppression
#![allow(unused_variables, dead_code)]
#![allow(dead_code)]
use std::any::Any;
use std::collections::HashMap;
@@ -32,6 +32,23 @@ where
}
}
/// Downcast an object pointer to a concrete type, and do something with that object.
pub fn with_obj_downcast_mut<T, Out>(ptr: ObjP, closure: impl FnOnce(&mut T) -> Out) -> Out
where
T: Obj + 'static,
{
let mut borrowed = ptr.borrow_mut();
if let Some(obj) = borrowed.as_any_mut().downcast_mut::<T>() {
closure(obj)
} else {
panic!(
"could not downcast '{:?}' to {}",
ptr,
std::any::type_name::<T>()
)
}
}
pub fn obj_is_inst<T>(ptr: &ObjP) -> bool
where
T: Obj + 'static,
@@ -50,34 +67,60 @@ pub fn upcast_obj<T: Obj>(ptr: Ptr<T>) -> ObjP {
pub fn init_types(builtins: &mut HashMap<String, ObjP>) {
#![allow(non_snake_case)]
macro_rules! types {
(base_type: $base_type:ident, $($name:ident),* $(,)?) => {
(
base_type: $base_type:ident,
$(
$name:ident {
$( $vtable_name:ident => $vtable_value:expr ),* $(,)?
}
),* $(,)?
) => {{
$(
let $name = make_ptr(TypeInst::new(stringify!($name)));
)*
$(
$name.borrow_mut().instantiate(upcast_obj(Gc::clone(&$base_type)));
)*
// We have to instantiate these objects all by hand. This is because the `instantiate`
// function does some stuff that may accidentally cause infinite recursion while we are
// setting up these fundamental types.
$({
let base_type = $base_type.clone();
$name.borrow_mut().set_attr("__type__", base_type);
with_obj_downcast_mut($name.clone(), |type_inst: &mut TypeInst| { type_inst.base.is_instantiated = true; });
//$name.borrow_mut().base.is_instantiated = true;
})*
$(
builtins.insert(stringify!($name).to_string(), upcast_obj($name));
builtins.insert(stringify!($name).to_string(), $name.clone());
)*
};
$({
$(
let vtable_name = stringify!($vtable_name);
let vtable_value = $vtable_value;
with_obj_downcast_mut($name, |type_inst: &mut TypeInst| {
type_inst.vtable.insert(vtable_name.to_string(), vtable_value);
});
)*
})*
}};
}
types! {
// base type
base_type: Type,
// type definitions
Type,
Obj,
Str,
Int,
Float,
Bool,
Nil,
BuiltinFunction,
UserFunction,
Method,
Type {
to_string => builtins.create_builtin_function("to_string", BaseObjInst::to_string, 1),
},
Obj { },
Str { },
Int { },
Float { },
Bool { },
Nil { },
BuiltinFunction { },
UserFunction { },
Method { },
}
}
@@ -85,50 +128,31 @@ pub trait ObjFactory {
fn builtins(&self) -> &HashMap<String, ObjP>;
fn create_obj(&self) -> ObjP {
upcast_obj(ObjInst::create(ObjP::clone(
self.builtins().get("Obj").unwrap(),
)))
ObjInst::create(self.builtins().get("Obj").unwrap().clone())
}
fn create_type(&self, name: impl ToString) -> ObjP {
upcast_obj(TypeInst::create(
ObjP::clone(self.builtins().get("Type").unwrap()),
name,
))
TypeInst::create(self.builtins().get("Type").unwrap().clone(), name)
}
fn create_str(&self, str_value: impl ToString) -> ObjP {
upcast_obj(StrInst::create(
ObjP::clone(self.builtins().get("Str").unwrap()),
str_value,
))
StrInst::create(self.builtins().get("Str").unwrap().clone(), str_value)
}
fn create_int(&self, int_value: i64) -> ObjP {
upcast_obj(IntInst::create(
ObjP::clone(self.builtins().get("Int").unwrap()),
int_value,
))
IntInst::create(self.builtins().get("Int").unwrap().clone(), int_value)
}
fn create_float(&self, float_value: f64) -> ObjP {
upcast_obj(FloatInst::create(
ObjP::clone(self.builtins().get("Float").unwrap()),
float_value,
))
FloatInst::create(self.builtins().get("Float").unwrap().clone(), float_value)
}
fn create_bool(&self, bool_value: bool) -> ObjP {
upcast_obj(BoolInst::create(
ObjP::clone(self.builtins().get("Bool").unwrap()),
bool_value,
))
BoolInst::create(self.builtins().get("Bool").unwrap().clone(), bool_value)
}
fn create_nil(&self) -> ObjP {
upcast_obj(NilInst::create(ObjP::clone(
self.builtins().get("Nil").unwrap(),
)))
NilInst::create(self.builtins().get("Nil").unwrap().clone())
}
fn create_builtin_function(
@@ -137,28 +161,28 @@ pub trait ObjFactory {
function: BuiltinFunctionPtr,
arity: Argc,
) -> ObjP {
upcast_obj(BuiltinFunctionInst::create(
ObjP::clone(self.builtins().get("BuiltinFunction").unwrap()),
BuiltinFunctionInst::create(
self.builtins().get("BuiltinFunction").unwrap().clone(),
name,
function,
arity,
))
)
}
fn create_user_function(&self, chunk: Chunk, arity: Argc) -> ObjP {
upcast_obj(UserFunctionInst::create(
ObjP::clone(self.builtins().get("UserFunction").unwrap()),
UserFunctionInst::create(
self.builtins().get("UserFunction").unwrap().clone(),
chunk,
arity,
))
)
}
fn create_method(&self, self_binding: ObjP, function: ObjP) -> ObjP {
upcast_obj(MethodInst::create(
ObjP::clone(self.builtins().get("Method").unwrap()),
MethodInst::create(
self.builtins().get("Method").unwrap().clone(),
self_binding,
function,
))
)
}
}
@@ -173,8 +197,8 @@ impl ObjFactory for HashMap<String, ObjP> {
///
/// I would implement this as a `From<T>` but it doesn't seem to work for a foreign type, and I'm
/// not sure why.
pub fn make_ptr<T: Obj>(obj: T) -> Ptr<T> {
Ptr::new(GcCell::new(obj))
pub fn make_ptr<T: Obj>(obj: T) -> ObjP {
upcast_obj(Ptr::new(GcCell::new(obj)))
}
////////////////////////////////////////////////////////////////////////////////
@@ -193,9 +217,49 @@ pub trait Obj: Debug + Display + Any + Trace {
}
fn get_attr(&self, name: &str) -> Option<ObjP> {
// check attrs, then check vtable
self.attrs().get(name).map(Ptr::clone)
}
/// This tries to get an attribute using these sources:
/// * `self.attrs()`
/// * `self.type_inst().vtable`
/// * `self.type_inst().type_inst().vtable` - our type's type's vtable (search up the type
/// inheritance tree)
///
/// If the value is found in a vtable, then it is inserted as an attribute. If it is a
/// function (BuiltinFunctionInst, UserFunctionInst), then it is wrapped in a `MethodInst`
/// first.
fn get_attr_lazy(&mut self, self_ptr: ObjP, method_ty: ObjP, name: &str) -> Option<ObjP> {
let attr = self.get_attr(name);
if attr.is_some() {
return attr;
}
let mut type_inst = self.type_inst();
loop {
with_obj_downcast_mut(type_inst.clone(), |type_inst: &mut TypeInst| {
type_inst.vtable.get(name).cloned()
})
.map(|vtable_entry| {
let ptr = if obj_is_inst::<BuiltinFunctionInst>(&vtable_entry)
|| obj_is_inst::<UserFunctionInst>(&vtable_entry)
{
MethodInst::create(method_ty.clone(), self_ptr.clone(), vtable_entry)
} else {
vtable_entry
};
self.set_attr(name, ptr.clone());
ptr
})?;
let type_inst_copy = type_inst.borrow().type_inst();
if type_inst.borrow().equals(&*type_inst_copy.borrow()) {
return None;
}
type_inst = type_inst_copy;
}
}
fn type_inst(&self) -> ObjP {
self.get_attr("__type__").unwrap()
}
@@ -210,7 +274,7 @@ pub trait Obj: Debug + Display + Any + Trace {
None
}
fn call(&self, vm: &mut Vm, argc: Argc) {
fn call(&self, _vm: &mut Vm, _argc: Argc) {
// TODO Obj::call - need to handle "this object cannot be called" errors
// BLOCKED-ON: exceptions
todo!("Raise some kind of not implemented/not callable error for non-callable objects")
@@ -237,6 +301,13 @@ struct BaseObjInst {
is_instantiated: bool,
}
impl BaseObjInst {
fn to_string(vm: &mut Vm, args: Vec<ObjP>) -> ObjP {
let str_value = format!("{}", &args[0].borrow());
vm.create_str(str_value)
}
}
impl Finalize for BaseObjInst {
fn finalize(&self) {}
}
@@ -259,31 +330,6 @@ impl Display for BaseObjInst {
impl Obj for BaseObjInst {
fn instantiate(&mut self, ty: ObjP) {
self.set_attr("__type__", ty);
// TODO BaseObjInst::instantiate - instantiate VTable
// Okay, we are running into a little snag here:
// * TypeInst::vtable holds a collection of named objects that will get copied into the
// attributes during instatiation.
// * If an object that gets copied is a function (UserFunctionInst, BuiltinFunctionInst),
// it will be wrapped by a MethodInst
// * MethodInst requires a pointer to the function being wrapped, as well as a pointer to
// the "self" object.
// * This is the root of the problem - ***we need the pointer to the object that we are
// currently instantiating.***
/*
let type_inst_ptr = Ptr::clone(&self.type_inst());
with_obj_downcast(type_inst_ptr, |type_inst: &TypeInst| {
for (key, value_ptr) in type_inst.vtable.iter() {
// copy functions over as MethodInst
if obj_is_inst::<BuiltinFunctionInst>(&value_ptr)
|| obj_is_inst::<UserFunctionInst>(&value_ptr)
{
self.set_attr(key, MethodInst::create(value_ptr));
}
}
});
*/
self.is_instantiated = true;
}
@@ -325,6 +371,10 @@ impl Obj for BaseObjInst {
macro_rules! impl_base_obj {
($base_name:ident) => {
fn instantiate(&mut self, ty: ObjP) {
self.$base_name.instantiate(ty);
}
fn is_instantiated(&self) -> bool {
self.$base_name.is_instantiated()
}
@@ -350,6 +400,16 @@ macro_rules! impl_base_obj {
};
}
macro_rules! impl_create {
($($arg:ident : $ty:ty),* $(,)?) => {
pub fn create(ty: ObjP $(, $arg : $ty )*) -> ObjP {
let ptr = make_ptr(Self::new($($arg),*));
ptr.borrow_mut().instantiate(ty);
ptr
}
}
}
////////////////////////////////////////////////////////////////////////////////
// ObjInst
////////////////////////////////////////////////////////////////////////////////
@@ -366,11 +426,7 @@ impl ObjInst {
}
}
pub fn create(ty: ObjP) -> Ptr<Self> {
let mut new = Self::new();
new.instantiate(ty);
make_ptr(new)
}
impl_create!();
}
impl Finalize for ObjInst {
@@ -384,10 +440,6 @@ impl Display for ObjInst {
}
impl Obj for ObjInst {
fn instantiate(&mut self, ty: ObjP) {
self.base.instantiate(ty);
}
fn equals(&self, other: &dyn Obj) -> bool {
if let Some(other) = other.as_any().downcast_ref::<ObjInst>() {
self.base.equals(&other.base)
@@ -424,11 +476,7 @@ impl TypeInst {
}
}
pub fn create(ty: ObjP, name: impl ToString) -> Ptr<Self> {
let mut new = Self::new(name);
new.instantiate(ty);
make_ptr(new)
}
impl_create!(name: impl ToString);
pub fn name(&self) -> &Rc<String> {
&self.name
@@ -458,10 +506,6 @@ impl Display for TypeInst {
}
impl Obj for TypeInst {
fn instantiate(&mut self, ty: ObjP) {
self.base.instantiate(ty);
}
fn equals(&self, other: &dyn Obj) -> bool {
if let Some(other) = other.as_any().downcast_ref::<TypeInst>() {
// TODO TypeInst::equals : something more robust than this
@@ -498,11 +542,7 @@ impl StrInst {
}
}
pub fn create(ty: ObjP, str_value: impl ToString) -> Ptr<Self> {
let mut new = Self::new(str_value);
new.instantiate(ty);
make_ptr(new)
}
impl_create!(str_value: impl ToString);
pub fn str_value(&self) -> &Rc<String> {
&self.str_value
@@ -520,10 +560,6 @@ impl Display for StrInst {
}
impl Obj for StrInst {
fn instantiate(&mut self, ty: ObjP) {
self.base.instantiate(ty);
}
fn is_truthy(&self) -> bool {
!self.str_value.is_empty()
}
@@ -557,11 +593,7 @@ impl IntInst {
}
}
pub fn create(ty: ObjP, int_value: i64) -> Ptr<Self> {
let mut new = Self::new(int_value);
new.instantiate(ty);
make_ptr(new)
}
impl_create!(int_value: i64);
pub fn int_value(&self) -> i64 {
self.int_value
@@ -579,10 +611,6 @@ impl Display for IntInst {
}
impl Obj for IntInst {
fn instantiate(&mut self, ty: ObjP) {
self.base.instantiate(ty);
}
fn is_truthy(&self) -> bool {
self.int_value != 0
}
@@ -618,11 +646,7 @@ impl FloatInst {
}
}
pub fn create(ty: ObjP, float_value: f64) -> Ptr<Self> {
let mut new = Self::new(float_value);
new.instantiate(ty);
make_ptr(new)
}
impl_create!(float_value: f64);
pub fn float_value(&self) -> f64 {
self.float_value
@@ -640,10 +664,6 @@ impl Display for FloatInst {
}
impl Obj for FloatInst {
fn instantiate(&mut self, ty: ObjP) {
self.base.instantiate(ty);
}
fn is_truthy(&self) -> bool {
self.float_value != 0.0
}
@@ -679,12 +699,7 @@ impl BoolInst {
}
}
pub fn create(ty: ObjP, bool_value: bool) -> Ptr<Self> {
// TODO BoolInst::create : interning
let mut new = Self::new(bool_value);
new.instantiate(ty);
make_ptr(new)
}
impl_create!(bool_value: bool);
pub fn bool_value(&self) -> bool {
self.bool_value
@@ -702,10 +717,6 @@ impl Display for BoolInst {
}
impl Obj for BoolInst {
fn instantiate(&mut self, ty: ObjP) {
self.base.instantiate(ty);
}
fn is_truthy(&self) -> bool {
self.bool_value
}
@@ -735,12 +746,7 @@ impl NilInst {
Default::default()
}
pub fn create(ty: ObjP) -> Ptr<Self> {
// TODO NilInst::create : interning
let mut new = Self::new();
new.instantiate(ty);
make_ptr(new)
}
impl_create!();
}
impl Finalize for NilInst {
@@ -754,10 +760,6 @@ impl Display for NilInst {
}
impl Obj for NilInst {
fn instantiate(&mut self, ty: ObjP) {
self.base.instantiate(ty);
}
fn is_truthy(&self) -> bool {
false
}
@@ -794,16 +796,11 @@ impl BuiltinFunctionInst {
}
}
pub fn create(
ty: ObjP,
impl_create!(
name: impl ToString,
function: BuiltinFunctionPtr,
arity: Argc,
) -> Ptr<Self> {
let mut new = Self::new(name, function, arity);
new.instantiate(ty);
make_ptr(new)
}
);
pub fn name(&self) -> &String {
&self.name
@@ -827,10 +824,6 @@ impl Display for BuiltinFunctionInst {
}
impl Obj for BuiltinFunctionInst {
fn instantiate(&mut self, ty: ObjP) {
self.base.instantiate(ty);
}
fn arity(&self) -> Option<Argc> {
Some(self.arity)
}
@@ -887,11 +880,7 @@ impl UserFunctionInst {
}
}
pub fn create(ty: ObjP, chunk: Chunk, arity: Argc) -> Ptr<Self> {
let mut new = Self::new(chunk, arity);
new.instantiate(ty);
make_ptr(new)
}
impl_create!(chunk: Chunk, arity: Argc);
pub fn name(&self) -> &String {
&self.name
@@ -927,10 +916,6 @@ impl Display for UserFunctionInst {
}
impl Obj for UserFunctionInst {
fn instantiate(&mut self, ty: ObjP) {
self.base.instantiate(ty);
}
fn arity(&self) -> Option<Argc> {
Some(self.arity)
}
@@ -981,10 +966,10 @@ impl MethodInst {
}
}
pub fn create(ty: ObjP, self_binding: ObjP, function: ObjP) -> Ptr<Self> {
let mut new = Self::new(self_binding, function);
new.instantiate(ty);
make_ptr(new)
pub fn create(ty: ObjP, self_binding: ObjP, function: ObjP) -> ObjP {
let ptr = make_ptr(Self::new(self_binding, function));
ptr.borrow_mut().instantiate(ty.clone());
ptr
}
pub fn self_binding(&self) -> &ObjP {
@@ -1003,10 +988,6 @@ impl Display for MethodInst {
}
impl Obj for MethodInst {
fn instantiate(&mut self, ty: ObjP) {
self.base.instantiate(ty);
}
fn arity(&self) -> Option<Argc> {
self.function.borrow().arity()
}
@@ -1083,10 +1064,47 @@ fn test_obj_equals() {
assert!(obj1.borrow().equals(&*obj2.borrow()));
// these objects aren't equal anymore
obj1.borrow_mut().set_attr("my_attr", ObjP::clone(&str2));
obj1.borrow_mut().set_attr("my_attr", str2.clone());
assert!(!obj1.borrow().equals(&*obj2.borrow()));
// but now they are!
obj2.borrow_mut().set_attr("my_attr", ObjP::clone(&str2));
obj2.borrow_mut().set_attr("my_attr", str2.clone());
assert!(obj2.borrow().equals(&*obj1.borrow()));
}
fn test_obj_vtable() {
let mut builtins = HashMap::new();
init_types(&mut builtins);
let str1 = builtins.create_str("asdfasdfasdf");
let to_string_ptr = str1.borrow_mut().get_attr_lazy(
str1.clone(),
builtins.get("Method").unwrap().clone(),
"to_string",
);
assert!(to_string_ptr.is_some());
let to_string_ptr = to_string_ptr.unwrap();
assert!(obj_is_inst::<MethodInst>(&to_string_ptr));
with_obj_downcast(to_string_ptr.clone(), |method: &MethodInst| {
assert!(method.self_binding.borrow().equals(&*str1.borrow()));
});
// now get the method's to_string ptr
let method_to_string_ptr = to_string_ptr.borrow_mut().get_attr_lazy(
to_string_ptr.clone(),
builtins.get("Method").unwrap().clone(),
"to_string",
);
assert!(method_to_string_ptr.is_some());
// this is like doing "asdfasdfasdf".to_string().to_string()
let method_to_string_ptr = method_to_string_ptr.unwrap();
assert!(obj_is_inst::<MethodInst>(&method_to_string_ptr));
with_obj_downcast(method_to_string_ptr.clone(), |method: &MethodInst| {
assert!(method
.self_binding
.borrow()
.equals(&*to_string_ptr.borrow()));
});
}