Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions mrubyedge/src/yamrb/prelude/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ pub(crate) fn initialize_object(vm: &mut VM) {
"method_missing",
Box::new(mrb_object_method_missing),
);
mrb_define_cmethod(
vm,
object_class.clone(),
"extend",
Box::new(mrb_object_extend),
);

// define global consts:
vm.consts.insert(
Expand Down Expand Up @@ -772,3 +778,40 @@ fn test_mrb_object_is_equal_instance() {
.expect("must return bool");
assert!(!ret);
}

fn mrb_object_extend(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
let this = vm.getself()?;

if args.is_empty() {
return Err(Error::ArgumentError(
"wrong number of arguments (given 0, expected 1+)".to_string(),
));
}

// Initialize or get singleton class
let singleton_class = this.initialize_or_get_singleton_class(vm);

// Extend with each module argument
for arg in args.iter().rev() {
let module = match &arg.value {
RValue::Module(m) => m.clone(),
RValue::Class(c) => c.module.clone(),
RValue::Nil => {
continue;
}
_ => {
return Err(Error::ArgumentError(
"wrong argument type (expected Module)".to_string(),
));
}
};

// Add module to extended_modules
singleton_class
.extended_modules
.borrow_mut()
.insert(0, module);
}

Ok(this)
}
36 changes: 31 additions & 5 deletions mrubyedge/src/yamrb/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -948,6 +948,7 @@ pub struct RClass {
pub super_class: Option<Rc<RClass>>,
pub singleton_class_ref: RefCell<Option<Rc<RClass>>>,
pub is_singleton: bool,
pub extended_modules: RefCell<Vec<Rc<RModule>>>,
}

impl RClass {
Expand All @@ -966,6 +967,7 @@ impl RClass {
super_class,
singleton_class_ref,
is_singleton: false,
extended_modules: RefCell::new(Vec::new()),
}
}

Expand All @@ -984,6 +986,7 @@ impl RClass {
super_class,
singleton_class_ref,
is_singleton: true,
extended_modules: RefCell::new(Vec::new()),
}
}

Expand All @@ -993,12 +996,26 @@ impl RClass {

// find_method will search method from self to superclass
pub fn find_method(&self, name: &str) -> Option<RProc> {
// First check this class's module
match self.module.find_method(name) {
Some(p) => Some(p),
None => match &self.super_class {
Some(sc) => sc.find_method(name),
None => None,
},
Some(p) => return Some(p),
None => {}
}

// For singleton classes, check extended modules
if self.is_singleton {
let extended = self.extended_modules.borrow();
for module in extended.iter() {
if let Some(p) = module.find_method(name) {
return Some(p);
}
}
}

// Finally check superclass
match &self.super_class {
Some(sc) => sc.find_method(name),
None => None,
}
}

Expand All @@ -1020,6 +1037,15 @@ fn collect_class_chain(
visited: &mut RHashSet<usize>,
) {
collect_module_chain(&class.module, chain, visited);

// For singleton classes, include extended modules
if class.is_singleton {
let extended = class.extended_modules.borrow();
for module in extended.iter() {
collect_module_chain(module, chain, visited);
}
}

if let Some(super_class) = &class.super_class {
collect_class_chain(super_class, chain, visited);
}
Expand Down
259 changes: 259 additions & 0 deletions mrubyedge/tests/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ extern crate mec_mrbc_sys;
extern crate mrubyedge;

mod helpers;
use std::rc::Rc;

use helpers::*;
use mrubyedge::yamrb::value::RObject;

#[test]
fn object_test() {
Expand Down Expand Up @@ -32,3 +35,259 @@ fn object_test() {
.unwrap();
assert_eq!(result, 1);
}

#[test]
fn object_extend_test() {
let code = r#"
module Greeter
def greet
"Hello from module"
end
end

module Farewell
def bye
"Goodbye from module"
end
end

def test_extend
obj = Object.new
obj.extend(Greeter)
result1 = obj.greet

obj.extend(Farewell)
result2 = obj.bye

[result1, result2]
end
"#;
let binary = mrbc_compile("extend", code);
let mut rite = mrubyedge::rite::load(&binary).unwrap();
let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite);
vm.run().unwrap();

let args = vec![];
let result = mrb_funcall(&mut vm, None, "test_extend", &args).unwrap();
let arr: Vec<Rc<RObject>> = result.as_ref().try_into().unwrap();
assert_eq!(
TryInto::<String>::try_into(arr[0].as_ref()).unwrap(),
"Hello from module"
);
assert_eq!(
TryInto::<String>::try_into(arr[1].as_ref()).unwrap(),
"Goodbye from module"
);
}

#[test]
fn object_extend_multiple_modules_test() {
let code = r#"
module M1
def m1_method
"from M1"
end
end

module M2
def m2_method
"from M2"
end
end

def test_extend_multiple
obj = Object.new
obj.extend(M1, M2)
[obj.m1_method, obj.m2_method]
end
"#;
let binary = mrbc_compile("extend_multiple", code);
let mut rite = mrubyedge::rite::load(&binary).unwrap();
let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite);
vm.run().unwrap();

let args = vec![];
let result = mrb_funcall(&mut vm, None, "test_extend_multiple", &args).unwrap();
let arr: Vec<Rc<RObject>> = result.as_ref().try_into().unwrap();
assert_eq!(
TryInto::<String>::try_into(arr[0].as_ref()).unwrap(),
"from M1"
);
assert_eq!(
TryInto::<String>::try_into(arr[1].as_ref()).unwrap(),
"from M2"
);
}

#[test]
fn object_extend_overrides_class_method_test() {
let code = r#"
class MyClass
def greet
"from class"
end
end

module MyModule
def greet
"from module"
end
end

def test_extend_override
obj = MyClass.new
obj.extend(MyModule)
obj.greet
end
"#;
let binary = mrbc_compile("extend_override_class", code);
let mut rite = mrubyedge::rite::load(&binary).unwrap();
let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite);
vm.run().unwrap();

let args = vec![];
let result = mrb_funcall(&mut vm, None, "test_extend_override", &args).unwrap();
let result_str: String = result.as_ref().try_into().unwrap();
assert_eq!(result_str, "from module");
}

#[test]
fn object_extend_singleton_method_priority_test() {
let code = r#"
module MyModule
def greet
"from module"
end
end

def test_singleton_priority
obj = Object.new
obj.extend(MyModule)

def obj.greet
"from singleton"
end

obj.greet
end
"#;
let binary = mrbc_compile("extend_singleton_priority", code);
let mut rite = mrubyedge::rite::load(&binary).unwrap();
let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite);
vm.run().unwrap();

let args = vec![];
let result = mrb_funcall(&mut vm, None, "test_singleton_priority", &args).unwrap();
let result_str: String = result.as_ref().try_into().unwrap();
assert_eq!(result_str, "from singleton");
}

#[test]
fn object_extend_multiple_priority_test() {
let code = r#"
module M1
def greet
"from M1"
end
end

module M2
def greet
"from M2"
end
end

module M3
def greet
"from M3"
end
end

def test_multiple_priority
obj = Object.new
obj.extend(M1)
result1 = obj.greet

obj.extend(M2)
result2 = obj.greet

obj.extend(M3)
result3 = obj.greet

[result1, result2, result3]
end
"#;
let binary = mrbc_compile("extend_multiple_priority", code);
let mut rite = mrubyedge::rite::load(&binary).unwrap();
let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite);
vm.run().unwrap();

let args = vec![];
let result = mrb_funcall(&mut vm, None, "test_multiple_priority", &args).unwrap();
let arr: Vec<Rc<RObject>> = result.as_ref().try_into().unwrap();
assert_eq!(
TryInto::<String>::try_into(arr[0].as_ref()).unwrap(),
"from M1"
);
assert_eq!(
TryInto::<String>::try_into(arr[1].as_ref()).unwrap(),
"from M2"
);
assert_eq!(
TryInto::<String>::try_into(arr[2].as_ref()).unwrap(),
"from M3"
);
}

#[test]
fn object_extend_multiple_arguments_priority_test() {
let code = r#"
module M1
def greet
"from M1"
end

def m1_only
"M1 only"
end
end

module M2
def greet
"from M2"
end

def m2_only
"M2 only"
end
end

def test_args_priority
obj = Object.new
# extend(M1, M2) extends in order: M2, then M1, so M1 has priority
obj.extend(M1, M2)
[obj.greet, obj.m1_only, obj.m2_only]
end
"#;
let binary = mrbc_compile("extend_args_priority", code);
let mut rite = mrubyedge::rite::load(&binary).unwrap();
let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite);
vm.run().unwrap();

let args = vec![];
let result = mrb_funcall(&mut vm, None, "test_args_priority", &args).unwrap();
let arr: Vec<Rc<RObject>> = result.as_ref().try_into().unwrap();
// M1 is extended last, so greet calls M1's method
assert_eq!(
TryInto::<String>::try_into(arr[0].as_ref()).unwrap(),
"from M1"
);
assert_eq!(
TryInto::<String>::try_into(arr[1].as_ref()).unwrap(),
"M1 only"
);
assert_eq!(
TryInto::<String>::try_into(arr[2].as_ref()).unwrap(),
"M2 only"
);
}