mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
b91b3bc771
Redo of34a2acdac7
and931138b006
which were reverted. GitHub PR #4340. This change implements a cache for class variables. Previously there was no cache for cvars. Cvar access is slow due to needing to travel all the way up th ancestor tree before returning the cvar value. The deeper the ancestor tree the slower cvar access will be. The benefits of the cache are more visible with a higher number of included modules due to the way Ruby looks up class variables. The benchmark here includes 26 modules and shows with the cache, this branch is 6.5x faster when accessing class variables. ``` compare-ruby: ruby 3.1.0dev (2021-03-15T06:22:34Z master9e5105c
) [x86_64-darwin19] built-ruby: ruby 3.1.0dev (2021-03-15T12:12:44Z add-cache-for-clas.. c6be009) [x86_64-darwin19] | |compare-ruby|built-ruby| |:--------|-----------:|---------:| |vm_cvar | 5.681M| 36.980M| | | -| 6.51x| ``` Benchmark.ips calling `ActiveRecord::Base.logger` from within a Rails application. ActiveRecord::Base.logger has 71 ancestors. The more ancestors a tree has, the more clear the speed increase. IE if Base had only one ancestor we'd see no improvement. This benchmark is run on a vanilla Rails application. Benchmark code: ```ruby require "benchmark/ips" require_relative "config/environment" Benchmark.ips do |x| x.report "logger" do ActiveRecord::Base.logger end end ``` Ruby 3.0 master / Rails 6.1: ``` Warming up -------------------------------------- logger 155.251k i/100ms Calculating ------------------------------------- ``` Ruby 3.0 with cvar cache / Rails 6.1: ``` Warming up -------------------------------------- logger 1.546M i/100ms Calculating ------------------------------------- logger 14.857M (± 4.8%) i/s - 74.198M in 5.006202s ``` Lastly we ran a benchmark to demonstate the difference between master and our cache when the number of modules increases. This benchmark measures 1 ancestor, 30 ancestors, and 100 ancestors. Ruby 3.0 master: ``` Warming up -------------------------------------- 1 module 1.231M i/100ms 30 modules 432.020k i/100ms 100 modules 145.399k i/100ms Calculating ------------------------------------- 1 module 12.210M (± 2.1%) i/s - 61.553M in 5.043400s 30 modules 4.354M (± 2.7%) i/s - 22.033M in 5.063839s 100 modules 1.434M (± 2.9%) i/s - 7.270M in 5.072531s Comparison: 1 module: 12209958.3 i/s 30 modules: 4354217.8 i/s - 2.80x (± 0.00) slower 100 modules: 1434447.3 i/s - 8.51x (± 0.00) slower ``` Ruby 3.0 with cvar cache: ``` Warming up -------------------------------------- 1 module 1.641M i/100ms 30 modules 1.655M i/100ms 100 modules 1.620M i/100ms Calculating ------------------------------------- 1 module 16.279M (± 3.8%) i/s - 82.038M in 5.046923s 30 modules 15.891M (± 3.9%) i/s - 79.459M in 5.007958s 100 modules 16.087M (± 3.6%) i/s - 81.005M in 5.041931s Comparison: 1 module: 16279458.0 i/s 100 modules: 16087484.6 i/s - same-ish: difference falls within error 30 modules: 15891406.2 i/s - same-ish: difference falls within error ``` Co-authored-by: Aaron Patterson <tenderlove@ruby-lang.org>
2374 lines
64 KiB
C
2374 lines
64 KiB
C
/**********************************************************************
|
|
|
|
class.c -
|
|
|
|
$Author$
|
|
created at: Tue Aug 10 15:05:44 JST 1993
|
|
|
|
Copyright (C) 1993-2007 Yukihiro Matsumoto
|
|
|
|
**********************************************************************/
|
|
|
|
/*!
|
|
* \defgroup class Classes and their hierarchy.
|
|
* \par Terminology
|
|
* - class: same as in Ruby.
|
|
* - singleton class: class for a particular object
|
|
* - eigenclass: = singleton class
|
|
* - metaclass: class of a class. metaclass is a kind of singleton class.
|
|
* - metametaclass: class of a metaclass.
|
|
* - meta^(n)-class: class of a meta^(n-1)-class.
|
|
* - attached object: A singleton class knows its unique instance.
|
|
* The instance is called the attached object for the singleton class.
|
|
* \{
|
|
*/
|
|
|
|
#include "ruby/internal/config.h"
|
|
#include <ctype.h>
|
|
|
|
#include "constant.h"
|
|
#include "debug_counter.h"
|
|
#include "id_table.h"
|
|
#include "internal.h"
|
|
#include "internal/class.h"
|
|
#include "internal/eval.h"
|
|
#include "internal/hash.h"
|
|
#include "internal/object.h"
|
|
#include "internal/string.h"
|
|
#include "internal/variable.h"
|
|
#include "ruby/st.h"
|
|
#include "vm_core.h"
|
|
|
|
#define id_attached id__attached__
|
|
|
|
#define METACLASS_OF(k) RBASIC(k)->klass
|
|
#define SET_METACLASS_OF(k, cls) RBASIC_SET_CLASS(k, cls)
|
|
|
|
RUBY_EXTERN rb_serial_t ruby_vm_global_cvar_state;
|
|
|
|
void
|
|
rb_class_subclass_add(VALUE super, VALUE klass)
|
|
{
|
|
rb_subclass_entry_t *entry, *head;
|
|
|
|
if (super && super != Qundef) {
|
|
entry = ALLOC(rb_subclass_entry_t);
|
|
entry->klass = klass;
|
|
entry->next = NULL;
|
|
|
|
head = RCLASS_SUBCLASSES(super);
|
|
if (head) {
|
|
entry->next = head;
|
|
RCLASS_PARENT_SUBCLASSES(head->klass) = &entry->next;
|
|
}
|
|
|
|
RCLASS_SUBCLASSES(super) = entry;
|
|
RCLASS_PARENT_SUBCLASSES(klass) = &RCLASS_SUBCLASSES(super);
|
|
}
|
|
}
|
|
|
|
static void
|
|
rb_module_add_to_subclasses_list(VALUE module, VALUE iclass)
|
|
{
|
|
rb_subclass_entry_t *entry, *head;
|
|
|
|
entry = ALLOC(rb_subclass_entry_t);
|
|
entry->klass = iclass;
|
|
entry->next = NULL;
|
|
|
|
head = RCLASS_SUBCLASSES(module);
|
|
if (head) {
|
|
entry->next = head;
|
|
RCLASS_MODULE_SUBCLASSES(head->klass) = &entry->next;
|
|
}
|
|
|
|
RCLASS_SUBCLASSES(module) = entry;
|
|
RCLASS_MODULE_SUBCLASSES(iclass) = &RCLASS_SUBCLASSES(module);
|
|
}
|
|
|
|
void
|
|
rb_class_remove_from_super_subclasses(VALUE klass)
|
|
{
|
|
rb_subclass_entry_t *entry;
|
|
|
|
if (RCLASS_PARENT_SUBCLASSES(klass)) {
|
|
entry = *RCLASS_PARENT_SUBCLASSES(klass);
|
|
|
|
*RCLASS_PARENT_SUBCLASSES(klass) = entry->next;
|
|
if (entry->next) {
|
|
RCLASS_PARENT_SUBCLASSES(entry->next->klass) = RCLASS_PARENT_SUBCLASSES(klass);
|
|
}
|
|
xfree(entry);
|
|
}
|
|
|
|
RCLASS_PARENT_SUBCLASSES(klass) = NULL;
|
|
}
|
|
|
|
void
|
|
rb_class_remove_from_module_subclasses(VALUE klass)
|
|
{
|
|
rb_subclass_entry_t *entry;
|
|
|
|
if (RCLASS_MODULE_SUBCLASSES(klass)) {
|
|
entry = *RCLASS_MODULE_SUBCLASSES(klass);
|
|
*RCLASS_MODULE_SUBCLASSES(klass) = entry->next;
|
|
|
|
if (entry->next) {
|
|
RCLASS_MODULE_SUBCLASSES(entry->next->klass) = RCLASS_MODULE_SUBCLASSES(klass);
|
|
}
|
|
|
|
xfree(entry);
|
|
}
|
|
|
|
RCLASS_MODULE_SUBCLASSES(klass) = NULL;
|
|
}
|
|
|
|
void
|
|
rb_class_foreach_subclass(VALUE klass, void (*f)(VALUE, VALUE), VALUE arg)
|
|
{
|
|
rb_subclass_entry_t *cur = RCLASS_SUBCLASSES(klass);
|
|
|
|
/* do not be tempted to simplify this loop into a for loop, the order of
|
|
operations is important here if `f` modifies the linked list */
|
|
while (cur) {
|
|
VALUE curklass = cur->klass;
|
|
cur = cur->next;
|
|
f(curklass, arg);
|
|
}
|
|
}
|
|
|
|
static void
|
|
class_detach_subclasses(VALUE klass, VALUE arg)
|
|
{
|
|
rb_class_remove_from_super_subclasses(klass);
|
|
}
|
|
|
|
void
|
|
rb_class_detach_subclasses(VALUE klass)
|
|
{
|
|
rb_class_foreach_subclass(klass, class_detach_subclasses, Qnil);
|
|
}
|
|
|
|
static void
|
|
class_detach_module_subclasses(VALUE klass, VALUE arg)
|
|
{
|
|
rb_class_remove_from_module_subclasses(klass);
|
|
}
|
|
|
|
void
|
|
rb_class_detach_module_subclasses(VALUE klass)
|
|
{
|
|
rb_class_foreach_subclass(klass, class_detach_module_subclasses, Qnil);
|
|
}
|
|
|
|
/**
|
|
* Allocates a struct RClass for a new class.
|
|
*
|
|
* \param flags initial value for basic.flags of the returned class.
|
|
* \param klass the class of the returned class.
|
|
* \return an uninitialized Class object.
|
|
* \pre \p klass must refer \c Class class or an ancestor of Class.
|
|
* \pre \code (flags | T_CLASS) != 0 \endcode
|
|
* \post the returned class can safely be \c #initialize 'd.
|
|
*
|
|
* \note this function is not Class#allocate.
|
|
*/
|
|
static VALUE
|
|
class_alloc(VALUE flags, VALUE klass)
|
|
{
|
|
size_t payload_size = 0;
|
|
|
|
#if USE_RVARGC
|
|
payload_size = sizeof(rb_classext_t);
|
|
#endif
|
|
|
|
RVARGC_NEWOBJ_OF(obj, struct RClass, klass, (flags & T_MASK) | FL_PROMOTED1 /* start from age == 2 */ | (RGENGC_WB_PROTECTED_CLASS ? FL_WB_PROTECTED : 0), payload_size);
|
|
|
|
#if USE_RVARGC
|
|
obj->ptr = (rb_classext_t *)rb_rvargc_payload_data_ptr((VALUE)obj + rb_slot_size());
|
|
RB_OBJ_WRITTEN(obj, Qundef, (VALUE)obj + rb_slot_size());
|
|
#else
|
|
obj->ptr = ZALLOC(rb_classext_t);
|
|
#endif
|
|
|
|
/* ZALLOC
|
|
RCLASS_IV_TBL(obj) = 0;
|
|
RCLASS_CONST_TBL(obj) = 0;
|
|
RCLASS_M_TBL(obj) = 0;
|
|
RCLASS_IV_INDEX_TBL(obj) = 0;
|
|
RCLASS_SET_SUPER((VALUE)obj, 0);
|
|
RCLASS_SUBCLASSES(obj) = NULL;
|
|
RCLASS_PARENT_SUBCLASSES(obj) = NULL;
|
|
RCLASS_MODULE_SUBCLASSES(obj) = NULL;
|
|
*/
|
|
RCLASS_SET_ORIGIN((VALUE)obj, (VALUE)obj);
|
|
RCLASS_SERIAL(obj) = rb_next_class_serial();
|
|
RB_OBJ_WRITE(obj, &RCLASS_REFINED_CLASS(obj), Qnil);
|
|
RCLASS_ALLOCATOR(obj) = 0;
|
|
|
|
return (VALUE)obj;
|
|
}
|
|
|
|
static void
|
|
RCLASS_M_TBL_INIT(VALUE c)
|
|
{
|
|
RCLASS_M_TBL(c) = rb_id_table_create(0);
|
|
}
|
|
|
|
/*!
|
|
* A utility function that wraps class_alloc.
|
|
*
|
|
* allocates a class and initializes safely.
|
|
* \param super a class from which the new class derives.
|
|
* \return a class object.
|
|
* \pre \a super must be a class.
|
|
* \post the metaclass of the new class is Class.
|
|
*/
|
|
VALUE
|
|
rb_class_boot(VALUE super)
|
|
{
|
|
VALUE klass = class_alloc(T_CLASS, rb_cClass);
|
|
|
|
RCLASS_SET_SUPER(klass, super);
|
|
RCLASS_M_TBL_INIT(klass);
|
|
|
|
return (VALUE)klass;
|
|
}
|
|
|
|
|
|
/*!
|
|
* Ensures a class can be derived from super.
|
|
*
|
|
* \param super a reference to an object.
|
|
* \exception TypeError if \a super is not a Class or \a super is a singleton class.
|
|
*/
|
|
void
|
|
rb_check_inheritable(VALUE super)
|
|
{
|
|
if (!RB_TYPE_P(super, T_CLASS)) {
|
|
rb_raise(rb_eTypeError, "superclass must be an instance of Class (given an instance of %"PRIsVALUE")",
|
|
rb_obj_class(super));
|
|
}
|
|
if (RBASIC(super)->flags & FL_SINGLETON) {
|
|
rb_raise(rb_eTypeError, "can't make subclass of singleton class");
|
|
}
|
|
if (super == rb_cClass) {
|
|
rb_raise(rb_eTypeError, "can't make subclass of Class");
|
|
}
|
|
}
|
|
|
|
|
|
/*!
|
|
* Creates a new class.
|
|
* \param super a class from which the new class derives.
|
|
* \exception TypeError \a super is not inheritable.
|
|
* \exception TypeError \a super is the Class class.
|
|
*/
|
|
VALUE
|
|
rb_class_new(VALUE super)
|
|
{
|
|
Check_Type(super, T_CLASS);
|
|
rb_check_inheritable(super);
|
|
return rb_class_boot(super);
|
|
}
|
|
|
|
static void
|
|
clone_method(VALUE old_klass, VALUE new_klass, ID mid, const rb_method_entry_t *me)
|
|
{
|
|
if (me->def->type == VM_METHOD_TYPE_ISEQ) {
|
|
rb_cref_t *new_cref;
|
|
rb_vm_rewrite_cref(me->def->body.iseq.cref, old_klass, new_klass, &new_cref);
|
|
rb_add_method_iseq(new_klass, mid, me->def->body.iseq.iseqptr, new_cref, METHOD_ENTRY_VISI(me));
|
|
}
|
|
else {
|
|
rb_method_entry_set(new_klass, mid, me, METHOD_ENTRY_VISI(me));
|
|
}
|
|
}
|
|
|
|
struct clone_method_arg {
|
|
VALUE new_klass;
|
|
VALUE old_klass;
|
|
};
|
|
|
|
static enum rb_id_table_iterator_result
|
|
clone_method_i(ID key, VALUE value, void *data)
|
|
{
|
|
const struct clone_method_arg *arg = (struct clone_method_arg *)data;
|
|
clone_method(arg->old_klass, arg->new_klass, key, (const rb_method_entry_t *)value);
|
|
return ID_TABLE_CONTINUE;
|
|
}
|
|
|
|
struct clone_const_arg {
|
|
VALUE klass;
|
|
struct rb_id_table *tbl;
|
|
};
|
|
|
|
static int
|
|
clone_const(ID key, const rb_const_entry_t *ce, struct clone_const_arg *arg)
|
|
{
|
|
rb_const_entry_t *nce = ALLOC(rb_const_entry_t);
|
|
MEMCPY(nce, ce, rb_const_entry_t, 1);
|
|
RB_OBJ_WRITTEN(arg->klass, Qundef, ce->value);
|
|
RB_OBJ_WRITTEN(arg->klass, Qundef, ce->file);
|
|
|
|
rb_id_table_insert(arg->tbl, key, (VALUE)nce);
|
|
return ID_TABLE_CONTINUE;
|
|
}
|
|
|
|
static enum rb_id_table_iterator_result
|
|
clone_const_i(ID key, VALUE value, void *data)
|
|
{
|
|
return clone_const(key, (const rb_const_entry_t *)value, data);
|
|
}
|
|
|
|
static void
|
|
class_init_copy_check(VALUE clone, VALUE orig)
|
|
{
|
|
if (orig == rb_cBasicObject) {
|
|
rb_raise(rb_eTypeError, "can't copy the root class");
|
|
}
|
|
if (RCLASS_SUPER(clone) != 0 || clone == rb_cBasicObject) {
|
|
rb_raise(rb_eTypeError, "already initialized class");
|
|
}
|
|
if (FL_TEST(orig, FL_SINGLETON)) {
|
|
rb_raise(rb_eTypeError, "can't copy singleton class");
|
|
}
|
|
}
|
|
|
|
static void
|
|
copy_tables(VALUE clone, VALUE orig)
|
|
{
|
|
if (RCLASS_IV_TBL(clone)) {
|
|
st_free_table(RCLASS_IV_TBL(clone));
|
|
RCLASS_IV_TBL(clone) = 0;
|
|
}
|
|
if (RCLASS_CONST_TBL(clone)) {
|
|
rb_free_const_table(RCLASS_CONST_TBL(clone));
|
|
RCLASS_CONST_TBL(clone) = 0;
|
|
}
|
|
RCLASS_M_TBL(clone) = 0;
|
|
if (RCLASS_IV_TBL(orig)) {
|
|
st_data_t id;
|
|
|
|
rb_iv_tbl_copy(clone, orig);
|
|
CONST_ID(id, "__tmp_classpath__");
|
|
st_delete(RCLASS_IV_TBL(clone), &id, 0);
|
|
CONST_ID(id, "__classpath__");
|
|
st_delete(RCLASS_IV_TBL(clone), &id, 0);
|
|
CONST_ID(id, "__classid__");
|
|
st_delete(RCLASS_IV_TBL(clone), &id, 0);
|
|
}
|
|
if (RCLASS_CONST_TBL(orig)) {
|
|
struct clone_const_arg arg;
|
|
|
|
arg.tbl = RCLASS_CONST_TBL(clone) = rb_id_table_create(0);
|
|
arg.klass = clone;
|
|
rb_id_table_foreach(RCLASS_CONST_TBL(orig), clone_const_i, &arg);
|
|
}
|
|
}
|
|
|
|
static bool ensure_origin(VALUE klass);
|
|
|
|
/* :nodoc: */
|
|
VALUE
|
|
rb_mod_init_copy(VALUE clone, VALUE orig)
|
|
{
|
|
if (RB_TYPE_P(clone, T_CLASS)) {
|
|
class_init_copy_check(clone, orig);
|
|
}
|
|
if (!OBJ_INIT_COPY(clone, orig)) return clone;
|
|
|
|
/* cloned flag is refer at constant inline cache
|
|
* see vm_get_const_key_cref() in vm_insnhelper.c
|
|
*/
|
|
FL_SET(clone, RCLASS_CLONED);
|
|
FL_SET(orig , RCLASS_CLONED);
|
|
|
|
if (!FL_TEST(CLASS_OF(clone), FL_SINGLETON)) {
|
|
RBASIC_SET_CLASS(clone, rb_singleton_class_clone(orig));
|
|
rb_singleton_class_attached(RBASIC(clone)->klass, (VALUE)clone);
|
|
}
|
|
RCLASS_ALLOCATOR(clone) = RCLASS_ALLOCATOR(orig);
|
|
copy_tables(clone, orig);
|
|
if (RCLASS_M_TBL(orig)) {
|
|
struct clone_method_arg arg;
|
|
arg.old_klass = orig;
|
|
arg.new_klass = clone;
|
|
RCLASS_M_TBL_INIT(clone);
|
|
rb_id_table_foreach(RCLASS_M_TBL(orig), clone_method_i, &arg);
|
|
}
|
|
|
|
if (RCLASS_ORIGIN(orig) == orig) {
|
|
RCLASS_SET_SUPER(clone, RCLASS_SUPER(orig));
|
|
}
|
|
else {
|
|
VALUE p = RCLASS_SUPER(orig);
|
|
VALUE orig_origin = RCLASS_ORIGIN(orig);
|
|
VALUE prev_clone_p = clone;
|
|
VALUE origin_stack = rb_ary_tmp_new(2);
|
|
VALUE origin[2];
|
|
VALUE clone_p = 0;
|
|
long origin_len;
|
|
int add_subclass;
|
|
VALUE clone_origin;
|
|
|
|
ensure_origin(clone);
|
|
clone_origin = RCLASS_ORIGIN(clone);
|
|
|
|
while (p && p != orig_origin) {
|
|
if (BUILTIN_TYPE(p) != T_ICLASS) {
|
|
rb_bug("non iclass between module/class and origin");
|
|
}
|
|
clone_p = class_alloc(RBASIC(p)->flags, RBASIC(p)->klass);
|
|
RCLASS_SET_SUPER(prev_clone_p, clone_p);
|
|
prev_clone_p = clone_p;
|
|
RCLASS_M_TBL(clone_p) = RCLASS_M_TBL(p);
|
|
RCLASS_CONST_TBL(clone_p) = RCLASS_CONST_TBL(p);
|
|
RCLASS_IV_TBL(clone_p) = RCLASS_IV_TBL(p);
|
|
RCLASS_ALLOCATOR(clone_p) = RCLASS_ALLOCATOR(p);
|
|
if (RB_TYPE_P(clone, T_CLASS)) {
|
|
RCLASS_SET_INCLUDER(clone_p, clone);
|
|
}
|
|
add_subclass = TRUE;
|
|
if (p != RCLASS_ORIGIN(p)) {
|
|
origin[0] = clone_p;
|
|
origin[1] = RCLASS_ORIGIN(p);
|
|
rb_ary_cat(origin_stack, origin, 2);
|
|
}
|
|
else if ((origin_len = RARRAY_LEN(origin_stack)) > 1 &&
|
|
RARRAY_AREF(origin_stack, origin_len - 1) == p) {
|
|
RCLASS_SET_ORIGIN(RARRAY_AREF(origin_stack, (origin_len -= 2)), clone_p);
|
|
RICLASS_SET_ORIGIN_SHARED_MTBL(clone_p);
|
|
rb_ary_resize(origin_stack, origin_len);
|
|
add_subclass = FALSE;
|
|
}
|
|
if (add_subclass) {
|
|
rb_module_add_to_subclasses_list(RBASIC(p)->klass, clone_p);
|
|
}
|
|
p = RCLASS_SUPER(p);
|
|
}
|
|
|
|
if (p == orig_origin) {
|
|
if (clone_p) {
|
|
RCLASS_SET_SUPER(clone_p, clone_origin);
|
|
RCLASS_SET_SUPER(clone_origin, RCLASS_SUPER(orig_origin));
|
|
}
|
|
copy_tables(clone_origin, orig_origin);
|
|
if (RCLASS_M_TBL(orig_origin)) {
|
|
struct clone_method_arg arg;
|
|
arg.old_klass = orig;
|
|
arg.new_klass = clone;
|
|
RCLASS_M_TBL_INIT(clone_origin);
|
|
rb_id_table_foreach(RCLASS_M_TBL(orig_origin), clone_method_i, &arg);
|
|
}
|
|
}
|
|
else {
|
|
rb_bug("no origin for class that has origin");
|
|
}
|
|
}
|
|
|
|
return clone;
|
|
}
|
|
|
|
VALUE
|
|
rb_singleton_class_clone(VALUE obj)
|
|
{
|
|
return rb_singleton_class_clone_and_attach(obj, Qundef);
|
|
}
|
|
|
|
// Clone and return the singleton class of `obj` if it has been created and is attached to `obj`.
|
|
VALUE
|
|
rb_singleton_class_clone_and_attach(VALUE obj, VALUE attach)
|
|
{
|
|
const VALUE klass = RBASIC(obj)->klass;
|
|
|
|
// Note that `rb_singleton_class()` can create situations where `klass` is
|
|
// attached to an object other than `obj`. In which case `obj` does not have
|
|
// a material singleton class attached yet and there is no singleton class
|
|
// to clone.
|
|
if (!(FL_TEST(klass, FL_SINGLETON) && rb_attr_get(klass, id_attached) == obj)) {
|
|
// nothing to clone
|
|
return klass;
|
|
}
|
|
else {
|
|
/* copy singleton(unnamed) class */
|
|
bool klass_of_clone_is_new;
|
|
VALUE clone = class_alloc(RBASIC(klass)->flags, 0);
|
|
|
|
if (BUILTIN_TYPE(obj) == T_CLASS) {
|
|
klass_of_clone_is_new = true;
|
|
RBASIC_SET_CLASS(clone, clone);
|
|
}
|
|
else {
|
|
VALUE klass_metaclass_clone = rb_singleton_class_clone(klass);
|
|
// When `METACLASS_OF(klass) == klass_metaclass_clone`, it means the
|
|
// recursive call did not clone `METACLASS_OF(klass)`.
|
|
klass_of_clone_is_new = (METACLASS_OF(klass) != klass_metaclass_clone);
|
|
RBASIC_SET_CLASS(clone, klass_metaclass_clone);
|
|
}
|
|
|
|
RCLASS_SET_SUPER(clone, RCLASS_SUPER(klass));
|
|
RCLASS_ALLOCATOR(clone) = RCLASS_ALLOCATOR(klass);
|
|
if (RCLASS_IV_TBL(klass)) {
|
|
rb_iv_tbl_copy(clone, klass);
|
|
}
|
|
if (RCLASS_CONST_TBL(klass)) {
|
|
struct clone_const_arg arg;
|
|
arg.tbl = RCLASS_CONST_TBL(clone) = rb_id_table_create(0);
|
|
arg.klass = clone;
|
|
rb_id_table_foreach(RCLASS_CONST_TBL(klass), clone_const_i, &arg);
|
|
}
|
|
if (attach != Qundef) {
|
|
rb_singleton_class_attached(clone, attach);
|
|
}
|
|
RCLASS_M_TBL_INIT(clone);
|
|
{
|
|
struct clone_method_arg arg;
|
|
arg.old_klass = klass;
|
|
arg.new_klass = clone;
|
|
rb_id_table_foreach(RCLASS_M_TBL(klass), clone_method_i, &arg);
|
|
}
|
|
if (klass_of_clone_is_new) {
|
|
rb_singleton_class_attached(RBASIC(clone)->klass, clone);
|
|
}
|
|
FL_SET(clone, FL_SINGLETON);
|
|
|
|
return clone;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Attach a object to a singleton class.
|
|
* @pre \a klass is the singleton class of \a obj.
|
|
*/
|
|
void
|
|
rb_singleton_class_attached(VALUE klass, VALUE obj)
|
|
{
|
|
if (FL_TEST(klass, FL_SINGLETON)) {
|
|
rb_class_ivar_set(klass, id_attached, obj);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* whether k is a meta^(n)-class of Class class
|
|
* @retval 1 if \a k is a meta^(n)-class of Class class (n >= 0)
|
|
* @retval 0 otherwise
|
|
*/
|
|
#define META_CLASS_OF_CLASS_CLASS_P(k) (METACLASS_OF(k) == (k))
|
|
|
|
static int
|
|
rb_singleton_class_has_metaclass_p(VALUE sklass)
|
|
{
|
|
return rb_attr_get(METACLASS_OF(sklass), id_attached) == sklass;
|
|
}
|
|
|
|
int
|
|
rb_singleton_class_internal_p(VALUE sklass)
|
|
{
|
|
return (RB_TYPE_P(rb_attr_get(sklass, id_attached), T_CLASS) &&
|
|
!rb_singleton_class_has_metaclass_p(sklass));
|
|
}
|
|
|
|
/*!
|
|
* whether k has a metaclass
|
|
* @retval 1 if \a k has a metaclass
|
|
* @retval 0 otherwise
|
|
*/
|
|
#define HAVE_METACLASS_P(k) \
|
|
(FL_TEST(METACLASS_OF(k), FL_SINGLETON) && \
|
|
rb_singleton_class_has_metaclass_p(k))
|
|
|
|
/*!
|
|
* ensures \a klass belongs to its own eigenclass.
|
|
* @return the eigenclass of \a klass
|
|
* @post \a klass belongs to the returned eigenclass.
|
|
* i.e. the attached object of the eigenclass is \a klass.
|
|
* @note this macro creates a new eigenclass if necessary.
|
|
*/
|
|
#define ENSURE_EIGENCLASS(klass) \
|
|
(HAVE_METACLASS_P(klass) ? METACLASS_OF(klass) : make_metaclass(klass))
|
|
|
|
|
|
/*!
|
|
* Creates a metaclass of \a klass
|
|
* \param klass a class
|
|
* \return created metaclass for the class
|
|
* \pre \a klass is a Class object
|
|
* \pre \a klass has no singleton class.
|
|
* \post the class of \a klass is the returned class.
|
|
* \post the returned class is meta^(n+1)-class when \a klass is a meta^(n)-klass for n >= 0
|
|
*/
|
|
static inline VALUE
|
|
make_metaclass(VALUE klass)
|
|
{
|
|
VALUE super;
|
|
VALUE metaclass = rb_class_boot(Qundef);
|
|
|
|
FL_SET(metaclass, FL_SINGLETON);
|
|
rb_singleton_class_attached(metaclass, klass);
|
|
|
|
if (META_CLASS_OF_CLASS_CLASS_P(klass)) {
|
|
SET_METACLASS_OF(klass, metaclass);
|
|
SET_METACLASS_OF(metaclass, metaclass);
|
|
}
|
|
else {
|
|
VALUE tmp = METACLASS_OF(klass); /* for a meta^(n)-class klass, tmp is meta^(n)-class of Class class */
|
|
SET_METACLASS_OF(klass, metaclass);
|
|
SET_METACLASS_OF(metaclass, ENSURE_EIGENCLASS(tmp));
|
|
}
|
|
|
|
super = RCLASS_SUPER(klass);
|
|
while (RB_TYPE_P(super, T_ICLASS)) super = RCLASS_SUPER(super);
|
|
RCLASS_SET_SUPER(metaclass, super ? ENSURE_EIGENCLASS(super) : rb_cClass);
|
|
|
|
return metaclass;
|
|
}
|
|
|
|
/*!
|
|
* Creates a singleton class for \a obj.
|
|
* \pre \a obj must not a immediate nor a special const.
|
|
* \pre \a obj must not a Class object.
|
|
* \pre \a obj has no singleton class.
|
|
*/
|
|
static inline VALUE
|
|
make_singleton_class(VALUE obj)
|
|
{
|
|
VALUE orig_class = RBASIC(obj)->klass;
|
|
VALUE klass = rb_class_boot(orig_class);
|
|
|
|
FL_SET(klass, FL_SINGLETON);
|
|
RBASIC_SET_CLASS(obj, klass);
|
|
rb_singleton_class_attached(klass, obj);
|
|
|
|
SET_METACLASS_OF(klass, METACLASS_OF(rb_class_real(orig_class)));
|
|
return klass;
|
|
}
|
|
|
|
|
|
static VALUE
|
|
boot_defclass(const char *name, VALUE super)
|
|
{
|
|
VALUE obj = rb_class_boot(super);
|
|
ID id = rb_intern(name);
|
|
|
|
rb_const_set((rb_cObject ? rb_cObject : obj), id, obj);
|
|
rb_vm_add_root_module(obj);
|
|
return obj;
|
|
}
|
|
|
|
void
|
|
Init_class_hierarchy(void)
|
|
{
|
|
rb_cBasicObject = boot_defclass("BasicObject", 0);
|
|
rb_cObject = boot_defclass("Object", rb_cBasicObject);
|
|
rb_gc_register_mark_object(rb_cObject);
|
|
|
|
/* resolve class name ASAP for order-independence */
|
|
rb_set_class_path_string(rb_cObject, rb_cObject, rb_fstring_lit("Object"));
|
|
|
|
rb_cModule = boot_defclass("Module", rb_cObject);
|
|
rb_cClass = boot_defclass("Class", rb_cModule);
|
|
|
|
rb_const_set(rb_cObject, rb_intern_const("BasicObject"), rb_cBasicObject);
|
|
RBASIC_SET_CLASS(rb_cClass, rb_cClass);
|
|
RBASIC_SET_CLASS(rb_cModule, rb_cClass);
|
|
RBASIC_SET_CLASS(rb_cObject, rb_cClass);
|
|
RBASIC_SET_CLASS(rb_cBasicObject, rb_cClass);
|
|
}
|
|
|
|
|
|
/*!
|
|
* \internal
|
|
* Creates a new *singleton class* for an object.
|
|
*
|
|
* \pre \a obj has no singleton class.
|
|
* \note DO NOT USE the function in an extension libraries. Use \ref rb_singleton_class.
|
|
* \param obj An object.
|
|
* \param unused ignored.
|
|
* \return The singleton class of the object.
|
|
*/
|
|
VALUE
|
|
rb_make_metaclass(VALUE obj, VALUE unused)
|
|
{
|
|
if (BUILTIN_TYPE(obj) == T_CLASS) {
|
|
return make_metaclass(obj);
|
|
}
|
|
else {
|
|
return make_singleton_class(obj);
|
|
}
|
|
}
|
|
|
|
|
|
/*!
|
|
* Defines a new class.
|
|
* \param id ignored
|
|
* \param super A class from which the new class will derive. NULL means \c Object class.
|
|
* \return the created class
|
|
* \throw TypeError if super is not a \c Class object.
|
|
*
|
|
* \note the returned class will not be associated with \a id.
|
|
* You must explicitly set a class name if necessary.
|
|
*/
|
|
VALUE
|
|
rb_define_class_id(ID id, VALUE super)
|
|
{
|
|
VALUE klass;
|
|
|
|
if (!super) super = rb_cObject;
|
|
klass = rb_class_new(super);
|
|
rb_make_metaclass(klass, RBASIC(super)->klass);
|
|
|
|
return klass;
|
|
}
|
|
|
|
|
|
/*!
|
|
* Calls Class#inherited.
|
|
* \param super A class which will be called #inherited.
|
|
* NULL means Object class.
|
|
* \param klass A Class object which derived from \a super
|
|
* \return the value \c Class#inherited's returns
|
|
* \pre Each of \a super and \a klass must be a \c Class object.
|
|
*/
|
|
MJIT_FUNC_EXPORTED VALUE
|
|
rb_class_inherited(VALUE super, VALUE klass)
|
|
{
|
|
ID inherited;
|
|
if (!super) super = rb_cObject;
|
|
CONST_ID(inherited, "inherited");
|
|
return rb_funcall(super, inherited, 1, klass);
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
* Defines a top-level class.
|
|
* \param name name of the class
|
|
* \param super a class from which the new class will derive.
|
|
* \return the created class
|
|
* \throw TypeError if the constant name \a name is already taken but
|
|
* the constant is not a \c Class.
|
|
* \throw TypeError if the class is already defined but the class can not
|
|
* be reopened because its superclass is not \a super.
|
|
* \throw ArgumentError if the \a super is NULL.
|
|
* \post top-level constant named \a name refers the returned class.
|
|
*
|
|
* \note if a class named \a name is already defined and its superclass is
|
|
* \a super, the function just returns the defined class.
|
|
*/
|
|
VALUE
|
|
rb_define_class(const char *name, VALUE super)
|
|
{
|
|
VALUE klass;
|
|
ID id;
|
|
|
|
id = rb_intern(name);
|
|
if (rb_const_defined(rb_cObject, id)) {
|
|
klass = rb_const_get(rb_cObject, id);
|
|
if (!RB_TYPE_P(klass, T_CLASS)) {
|
|
rb_raise(rb_eTypeError, "%s is not a class (%"PRIsVALUE")",
|
|
name, rb_obj_class(klass));
|
|
}
|
|
if (rb_class_real(RCLASS_SUPER(klass)) != super) {
|
|
rb_raise(rb_eTypeError, "superclass mismatch for class %s", name);
|
|
}
|
|
|
|
/* Class may have been defined in Ruby and not pin-rooted */
|
|
rb_vm_add_root_module(klass);
|
|
return klass;
|
|
}
|
|
if (!super) {
|
|
rb_raise(rb_eArgError, "no super class for `%s'", name);
|
|
}
|
|
klass = rb_define_class_id(id, super);
|
|
rb_vm_add_root_module(klass);
|
|
rb_const_set(rb_cObject, id, klass);
|
|
rb_class_inherited(super, klass);
|
|
|
|
return klass;
|
|
}
|
|
|
|
|
|
/*!
|
|
* Defines a class under the namespace of \a outer.
|
|
* \param outer a class which contains the new class.
|
|
* \param name name of the new class
|
|
* \param super a class from which the new class will derive.
|
|
* NULL means \c Object class.
|
|
* \return the created class
|
|
* \throw TypeError if the constant name \a name is already taken but
|
|
* the constant is not a \c Class.
|
|
* \throw TypeError if the class is already defined but the class can not
|
|
* be reopened because its superclass is not \a super.
|
|
* \post top-level constant named \a name refers the returned class.
|
|
*
|
|
* \note if a class named \a name is already defined and its superclass is
|
|
* \a super, the function just returns the defined class.
|
|
* \note the compaction GC does not move classes returned by this function.
|
|
*/
|
|
VALUE
|
|
rb_define_class_under(VALUE outer, const char *name, VALUE super)
|
|
{
|
|
return rb_define_class_id_under(outer, rb_intern(name), super);
|
|
}
|
|
|
|
|
|
/*!
|
|
* Defines a class under the namespace of \a outer.
|
|
* \param outer a class which contains the new class.
|
|
* \param id name of the new class
|
|
* \param super a class from which the new class will derive.
|
|
* NULL means \c Object class.
|
|
* \return the created class
|
|
* \throw TypeError if the constant name \a name is already taken but
|
|
* the constant is not a \c Class.
|
|
* \throw TypeError if the class is already defined but the class can not
|
|
* be reopened because its superclass is not \a super.
|
|
* \post top-level constant named \a name refers the returned class.
|
|
*
|
|
* \note if a class named \a name is already defined and its superclass is
|
|
* \a super, the function just returns the defined class.
|
|
* \note the compaction GC does not move classes returned by this function.
|
|
*/
|
|
VALUE
|
|
rb_define_class_id_under(VALUE outer, ID id, VALUE super)
|
|
{
|
|
VALUE klass;
|
|
|
|
if (rb_const_defined_at(outer, id)) {
|
|
klass = rb_const_get_at(outer, id);
|
|
if (!RB_TYPE_P(klass, T_CLASS)) {
|
|
rb_raise(rb_eTypeError, "%"PRIsVALUE"::%"PRIsVALUE" is not a class"
|
|
" (%"PRIsVALUE")",
|
|
outer, rb_id2str(id), rb_obj_class(klass));
|
|
}
|
|
if (rb_class_real(RCLASS_SUPER(klass)) != super) {
|
|
rb_raise(rb_eTypeError, "superclass mismatch for class "
|
|
"%"PRIsVALUE"::%"PRIsVALUE""
|
|
" (%"PRIsVALUE" is given but was %"PRIsVALUE")",
|
|
outer, rb_id2str(id), RCLASS_SUPER(klass), super);
|
|
}
|
|
/* Class may have been defined in Ruby and not pin-rooted */
|
|
rb_vm_add_root_module(klass);
|
|
|
|
return klass;
|
|
}
|
|
if (!super) {
|
|
rb_raise(rb_eArgError, "no super class for `%"PRIsVALUE"::%"PRIsVALUE"'",
|
|
rb_class_path(outer), rb_id2str(id));
|
|
}
|
|
klass = rb_define_class_id(id, super);
|
|
rb_set_class_path_string(klass, outer, rb_id2str(id));
|
|
rb_const_set(outer, id, klass);
|
|
rb_class_inherited(super, klass);
|
|
rb_vm_add_root_module(klass);
|
|
|
|
return klass;
|
|
}
|
|
|
|
VALUE
|
|
rb_module_new(void)
|
|
{
|
|
VALUE mdl = class_alloc(T_MODULE, rb_cModule);
|
|
RCLASS_M_TBL_INIT(mdl);
|
|
return (VALUE)mdl;
|
|
}
|
|
|
|
// Kept for compatibility. Use rb_module_new() instead.
|
|
VALUE
|
|
rb_define_module_id(ID id)
|
|
{
|
|
return rb_module_new();
|
|
}
|
|
|
|
/*!
|
|
* \note the compaction GC does not move modules returned by this function.
|
|
*/
|
|
VALUE
|
|
rb_define_module(const char *name)
|
|
{
|
|
VALUE module;
|
|
ID id;
|
|
|
|
id = rb_intern(name);
|
|
if (rb_const_defined(rb_cObject, id)) {
|
|
module = rb_const_get(rb_cObject, id);
|
|
if (!RB_TYPE_P(module, T_MODULE)) {
|
|
rb_raise(rb_eTypeError, "%s is not a module (%"PRIsVALUE")",
|
|
name, rb_obj_class(module));
|
|
}
|
|
/* Module may have been defined in Ruby and not pin-rooted */
|
|
rb_vm_add_root_module(module);
|
|
return module;
|
|
}
|
|
module = rb_module_new();
|
|
rb_vm_add_root_module(module);
|
|
rb_const_set(rb_cObject, id, module);
|
|
|
|
return module;
|
|
}
|
|
|
|
/*!
|
|
* \note the compaction GC does not move modules returned by this function.
|
|
*/
|
|
VALUE
|
|
rb_define_module_under(VALUE outer, const char *name)
|
|
{
|
|
return rb_define_module_id_under(outer, rb_intern(name));
|
|
}
|
|
|
|
VALUE
|
|
rb_define_module_id_under(VALUE outer, ID id)
|
|
{
|
|
VALUE module;
|
|
|
|
if (rb_const_defined_at(outer, id)) {
|
|
module = rb_const_get_at(outer, id);
|
|
if (!RB_TYPE_P(module, T_MODULE)) {
|
|
rb_raise(rb_eTypeError, "%"PRIsVALUE"::%"PRIsVALUE" is not a module"
|
|
" (%"PRIsVALUE")",
|
|
outer, rb_id2str(id), rb_obj_class(module));
|
|
}
|
|
/* Module may have been defined in Ruby and not pin-rooted */
|
|
rb_gc_register_mark_object(module);
|
|
return module;
|
|
}
|
|
module = rb_module_new();
|
|
rb_const_set(outer, id, module);
|
|
rb_set_class_path_string(module, outer, rb_id2str(id));
|
|
rb_gc_register_mark_object(module);
|
|
|
|
return module;
|
|
}
|
|
|
|
VALUE
|
|
rb_include_class_new(VALUE module, VALUE super)
|
|
{
|
|
VALUE klass = class_alloc(T_ICLASS, rb_cClass);
|
|
|
|
RCLASS_M_TBL(klass) = RCLASS_M_TBL(module);
|
|
|
|
RCLASS_SET_ORIGIN(klass, klass);
|
|
if (BUILTIN_TYPE(module) == T_ICLASS) {
|
|
module = RBASIC(module)->klass;
|
|
}
|
|
RUBY_ASSERT(!RB_TYPE_P(module, T_ICLASS));
|
|
if (!RCLASS_IV_TBL(module)) {
|
|
RCLASS_IV_TBL(module) = st_init_numtable();
|
|
}
|
|
if (!RCLASS_CONST_TBL(module)) {
|
|
RCLASS_CONST_TBL(module) = rb_id_table_create(0);
|
|
}
|
|
RCLASS_IV_TBL(klass) = RCLASS_IV_TBL(module);
|
|
RCLASS_CVC_TBL(klass) = RCLASS_CVC_TBL(module);
|
|
RCLASS_CONST_TBL(klass) = RCLASS_CONST_TBL(module);
|
|
|
|
RCLASS_SET_SUPER(klass, super);
|
|
RBASIC_SET_CLASS(klass, module);
|
|
|
|
return (VALUE)klass;
|
|
}
|
|
|
|
static int include_modules_at(const VALUE klass, VALUE c, VALUE module, int search_super);
|
|
|
|
static void
|
|
ensure_includable(VALUE klass, VALUE module)
|
|
{
|
|
rb_class_modify_check(klass);
|
|
Check_Type(module, T_MODULE);
|
|
if (!NIL_P(rb_refinement_module_get_refined_class(module))) {
|
|
rb_raise(rb_eArgError, "refinement module is not allowed");
|
|
}
|
|
}
|
|
|
|
void
|
|
rb_include_module(VALUE klass, VALUE module)
|
|
{
|
|
int changed = 0;
|
|
|
|
ensure_includable(klass, module);
|
|
|
|
changed = include_modules_at(klass, RCLASS_ORIGIN(klass), module, TRUE);
|
|
if (changed < 0)
|
|
rb_raise(rb_eArgError, "cyclic include detected");
|
|
|
|
if (RB_TYPE_P(klass, T_MODULE)) {
|
|
rb_subclass_entry_t *iclass = RCLASS_SUBCLASSES(klass);
|
|
int do_include = 1;
|
|
while (iclass) {
|
|
VALUE check_class = iclass->klass;
|
|
while (check_class) {
|
|
if (RB_TYPE_P(check_class, T_ICLASS) &&
|
|
(RBASIC(check_class)->klass == module)) {
|
|
do_include = 0;
|
|
}
|
|
check_class = RCLASS_SUPER(check_class);
|
|
}
|
|
|
|
if (do_include) {
|
|
include_modules_at(iclass->klass, RCLASS_ORIGIN(iclass->klass), module, TRUE);
|
|
}
|
|
iclass = iclass->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
static enum rb_id_table_iterator_result
|
|
add_refined_method_entry_i(ID key, VALUE value, void *data)
|
|
{
|
|
rb_add_refined_method_entry((VALUE)data, key);
|
|
return ID_TABLE_CONTINUE;
|
|
}
|
|
|
|
static enum rb_id_table_iterator_result
|
|
clear_module_cache_i(ID id, VALUE val, void *data)
|
|
{
|
|
VALUE klass = (VALUE)data;
|
|
rb_clear_method_cache(klass, id);
|
|
return ID_TABLE_CONTINUE;
|
|
}
|
|
|
|
static bool
|
|
module_in_super_chain(const VALUE klass, VALUE module)
|
|
{
|
|
struct rb_id_table *const klass_m_tbl = RCLASS_M_TBL(RCLASS_ORIGIN(klass));
|
|
if (klass_m_tbl) {
|
|
while (module) {
|
|
if (klass_m_tbl == RCLASS_M_TBL(module))
|
|
return true;
|
|
module = RCLASS_SUPER(module);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static int
|
|
do_include_modules_at(const VALUE klass, VALUE c, VALUE module, int search_super, bool check_cyclic)
|
|
{
|
|
VALUE p, iclass, origin_stack = 0;
|
|
int method_changed = 0, constant_changed = 0, add_subclass;
|
|
long origin_len;
|
|
VALUE klass_origin = RCLASS_ORIGIN(klass);
|
|
VALUE original_klass = klass;
|
|
|
|
if (check_cyclic && module_in_super_chain(klass, module))
|
|
return -1;
|
|
|
|
while (module) {
|
|
int c_seen = FALSE;
|
|
int superclass_seen = FALSE;
|
|
struct rb_id_table *tbl;
|
|
|
|
if (klass == c) {
|
|
c_seen = TRUE;
|
|
}
|
|
if (klass_origin != c || search_super) {
|
|
/* ignore if the module included already in superclasses for include,
|
|
* ignore if the module included before origin class for prepend
|
|
*/
|
|
for (p = RCLASS_SUPER(klass); p; p = RCLASS_SUPER(p)) {
|
|
int type = BUILTIN_TYPE(p);
|
|
if (klass_origin == p && !search_super)
|
|
break;
|
|
if (c == p)
|
|
c_seen = TRUE;
|
|
if (type == T_ICLASS) {
|
|
if (RCLASS_M_TBL(p) == RCLASS_M_TBL(module)) {
|
|
if (!superclass_seen && c_seen) {
|
|
c = p; /* move insertion point */
|
|
}
|
|
goto skip;
|
|
}
|
|
}
|
|
else if (type == T_CLASS) {
|
|
superclass_seen = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
VALUE super_class = RCLASS_SUPER(c);
|
|
|
|
// invalidate inline method cache
|
|
RB_DEBUG_COUNTER_INC(cvar_include_invalidate);
|
|
ruby_vm_global_cvar_state++;
|
|
tbl = RCLASS_M_TBL(module);
|
|
if (tbl && rb_id_table_size(tbl)) {
|
|
if (search_super) { // include
|
|
if (super_class && !RB_TYPE_P(super_class, T_MODULE)) {
|
|
rb_id_table_foreach(tbl, clear_module_cache_i, (void *)super_class);
|
|
}
|
|
}
|
|
else { // prepend
|
|
if (!RB_TYPE_P(original_klass, T_MODULE)) {
|
|
rb_id_table_foreach(tbl, clear_module_cache_i, (void *)original_klass);
|
|
}
|
|
}
|
|
method_changed = 1;
|
|
}
|
|
|
|
// setup T_ICLASS for the include/prepend module
|
|
iclass = rb_include_class_new(module, super_class);
|
|
c = RCLASS_SET_SUPER(c, iclass);
|
|
RCLASS_SET_INCLUDER(iclass, klass);
|
|
add_subclass = TRUE;
|
|
if (module != RCLASS_ORIGIN(module)) {
|
|
if (!origin_stack) origin_stack = rb_ary_tmp_new(2);
|
|
VALUE origin[2] = {iclass, RCLASS_ORIGIN(module)};
|
|
rb_ary_cat(origin_stack, origin, 2);
|
|
}
|
|
else if (origin_stack && (origin_len = RARRAY_LEN(origin_stack)) > 1 &&
|
|
RARRAY_AREF(origin_stack, origin_len - 1) == module) {
|
|
RCLASS_SET_ORIGIN(RARRAY_AREF(origin_stack, (origin_len -= 2)), iclass);
|
|
RICLASS_SET_ORIGIN_SHARED_MTBL(iclass);
|
|
rb_ary_resize(origin_stack, origin_len);
|
|
add_subclass = FALSE;
|
|
}
|
|
|
|
{
|
|
VALUE m = module;
|
|
if (BUILTIN_TYPE(m) == T_ICLASS) m = RBASIC(m)->klass;
|
|
if (add_subclass) rb_module_add_to_subclasses_list(m, iclass);
|
|
}
|
|
|
|
if (FL_TEST(klass, RMODULE_IS_REFINEMENT)) {
|
|
VALUE refined_class =
|
|
rb_refinement_module_get_refined_class(klass);
|
|
|
|
rb_id_table_foreach(RCLASS_M_TBL(module), add_refined_method_entry_i, (void *)refined_class);
|
|
FL_SET(c, RMODULE_INCLUDED_INTO_REFINEMENT);
|
|
}
|
|
|
|
tbl = RCLASS_CONST_TBL(module);
|
|
if (tbl && rb_id_table_size(tbl)) constant_changed = 1;
|
|
skip:
|
|
module = RCLASS_SUPER(module);
|
|
}
|
|
|
|
if (constant_changed) rb_clear_constant_cache();
|
|
|
|
return method_changed;
|
|
}
|
|
|
|
static int
|
|
include_modules_at(const VALUE klass, VALUE c, VALUE module, int search_super)
|
|
{
|
|
return do_include_modules_at(klass, c, module, search_super, true);
|
|
}
|
|
|
|
static enum rb_id_table_iterator_result
|
|
move_refined_method(ID key, VALUE value, void *data)
|
|
{
|
|
rb_method_entry_t *me = (rb_method_entry_t *)value;
|
|
|
|
if (me->def->type == VM_METHOD_TYPE_REFINED) {
|
|
VALUE klass = (VALUE)data;
|
|
struct rb_id_table *tbl = RCLASS_M_TBL(klass);
|
|
|
|
if (me->def->body.refined.orig_me) {
|
|
const rb_method_entry_t *orig_me = me->def->body.refined.orig_me, *new_me;
|
|
RB_OBJ_WRITE(me, &me->def->body.refined.orig_me, NULL);
|
|
new_me = rb_method_entry_clone(me);
|
|
rb_method_table_insert(klass, tbl, key, new_me);
|
|
rb_method_entry_copy(me, orig_me);
|
|
return ID_TABLE_CONTINUE;
|
|
}
|
|
else {
|
|
rb_method_table_insert(klass, tbl, key, me);
|
|
return ID_TABLE_DELETE;
|
|
}
|
|
}
|
|
else {
|
|
return ID_TABLE_CONTINUE;
|
|
}
|
|
}
|
|
|
|
static enum rb_id_table_iterator_result
|
|
cache_clear_refined_method(ID key, VALUE value, void *data)
|
|
{
|
|
rb_method_entry_t *me = (rb_method_entry_t *) value;
|
|
|
|
if (me->def->type == VM_METHOD_TYPE_REFINED && me->def->body.refined.orig_me) {
|
|
VALUE klass = (VALUE)data;
|
|
rb_clear_method_cache(klass, me->called_id);
|
|
}
|
|
// Refined method entries without an orig_me is going to stay in the method
|
|
// table of klass, like before the move, so no need to clear the cache.
|
|
|
|
return ID_TABLE_CONTINUE;
|
|
}
|
|
|
|
static bool
|
|
ensure_origin(VALUE klass)
|
|
{
|
|
VALUE origin = RCLASS_ORIGIN(klass);
|
|
if (origin == klass) {
|
|
origin = class_alloc(T_ICLASS, klass);
|
|
RCLASS_SET_SUPER(origin, RCLASS_SUPER(klass));
|
|
RCLASS_SET_SUPER(klass, origin);
|
|
RCLASS_SET_ORIGIN(klass, origin);
|
|
RCLASS_M_TBL(origin) = RCLASS_M_TBL(klass);
|
|
RCLASS_M_TBL_INIT(klass);
|
|
rb_id_table_foreach(RCLASS_M_TBL(origin), cache_clear_refined_method, (void *)klass);
|
|
rb_id_table_foreach(RCLASS_M_TBL(origin), move_refined_method, (void *)klass);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
rb_prepend_module(VALUE klass, VALUE module)
|
|
{
|
|
int changed;
|
|
bool klass_had_no_origin;
|
|
|
|
ensure_includable(klass, module);
|
|
if (module_in_super_chain(klass, module))
|
|
rb_raise(rb_eArgError, "cyclic prepend detected");
|
|
|
|
klass_had_no_origin = ensure_origin(klass);
|
|
changed = do_include_modules_at(klass, klass, module, FALSE, false);
|
|
RUBY_ASSERT(changed >= 0); // already checked for cyclic prepend above
|
|
if (changed) {
|
|
rb_vm_check_redefinition_by_prepend(klass);
|
|
}
|
|
if (RB_TYPE_P(klass, T_MODULE)) {
|
|
rb_subclass_entry_t *iclass = RCLASS_SUBCLASSES(klass);
|
|
VALUE klass_origin = RCLASS_ORIGIN(klass);
|
|
struct rb_id_table *klass_m_tbl = RCLASS_M_TBL(klass);
|
|
struct rb_id_table *klass_origin_m_tbl = RCLASS_M_TBL(klass_origin);
|
|
while (iclass) {
|
|
if (klass_had_no_origin && klass_origin_m_tbl == RCLASS_M_TBL(iclass->klass)) {
|
|
// backfill an origin iclass to handle refinements and future prepends
|
|
rb_id_table_foreach(RCLASS_M_TBL(iclass->klass), clear_module_cache_i, (void *)iclass->klass);
|
|
RCLASS_M_TBL(iclass->klass) = klass_m_tbl;
|
|
VALUE origin = rb_include_class_new(klass_origin, RCLASS_SUPER(iclass->klass));
|
|
RCLASS_SET_SUPER(iclass->klass, origin);
|
|
RCLASS_SET_INCLUDER(origin, RCLASS_INCLUDER(iclass->klass));
|
|
RCLASS_SET_ORIGIN(iclass->klass, origin);
|
|
RICLASS_SET_ORIGIN_SHARED_MTBL(origin);
|
|
}
|
|
include_modules_at(iclass->klass, iclass->klass, module, FALSE);
|
|
iclass = iclass->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* mod.included_modules -> array
|
|
*
|
|
* Returns the list of modules included or prepended in <i>mod</i>
|
|
* or one of <i>mod</i>'s ancestors.
|
|
*
|
|
* module Sub
|
|
* end
|
|
*
|
|
* module Mixin
|
|
* prepend Sub
|
|
* end
|
|
*
|
|
* module Outer
|
|
* include Mixin
|
|
* end
|
|
*
|
|
* Mixin.included_modules #=> [Sub]
|
|
* Outer.included_modules #=> [Sub, Mixin]
|
|
*/
|
|
|
|
VALUE
|
|
rb_mod_included_modules(VALUE mod)
|
|
{
|
|
VALUE ary = rb_ary_new();
|
|
VALUE p;
|
|
VALUE origin = RCLASS_ORIGIN(mod);
|
|
|
|
for (p = RCLASS_SUPER(mod); p; p = RCLASS_SUPER(p)) {
|
|
if (p != origin && RCLASS_ORIGIN(p) == p && BUILTIN_TYPE(p) == T_ICLASS) {
|
|
VALUE m = RBASIC(p)->klass;
|
|
if (RB_TYPE_P(m, T_MODULE))
|
|
rb_ary_push(ary, m);
|
|
}
|
|
}
|
|
return ary;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* mod.include?(module) -> true or false
|
|
*
|
|
* Returns <code>true</code> if <i>module</i> is included
|
|
* or prepended in <i>mod</i> or one of <i>mod</i>'s ancestors.
|
|
*
|
|
* module A
|
|
* end
|
|
* class B
|
|
* include A
|
|
* end
|
|
* class C < B
|
|
* end
|
|
* B.include?(A) #=> true
|
|
* C.include?(A) #=> true
|
|
* A.include?(A) #=> false
|
|
*/
|
|
|
|
VALUE
|
|
rb_mod_include_p(VALUE mod, VALUE mod2)
|
|
{
|
|
VALUE p;
|
|
|
|
Check_Type(mod2, T_MODULE);
|
|
for (p = RCLASS_SUPER(mod); p; p = RCLASS_SUPER(p)) {
|
|
if (BUILTIN_TYPE(p) == T_ICLASS && !FL_TEST(p, RICLASS_IS_ORIGIN)) {
|
|
if (RBASIC(p)->klass == mod2) return Qtrue;
|
|
}
|
|
}
|
|
return Qfalse;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* mod.ancestors -> array
|
|
*
|
|
* Returns a list of modules included/prepended in <i>mod</i>
|
|
* (including <i>mod</i> itself).
|
|
*
|
|
* module Mod
|
|
* include Math
|
|
* include Comparable
|
|
* prepend Enumerable
|
|
* end
|
|
*
|
|
* Mod.ancestors #=> [Enumerable, Mod, Comparable, Math]
|
|
* Math.ancestors #=> [Math]
|
|
* Enumerable.ancestors #=> [Enumerable]
|
|
*/
|
|
|
|
VALUE
|
|
rb_mod_ancestors(VALUE mod)
|
|
{
|
|
VALUE p, ary = rb_ary_new();
|
|
|
|
for (p = mod; p; p = RCLASS_SUPER(p)) {
|
|
if (p != RCLASS_ORIGIN(p)) continue;
|
|
if (BUILTIN_TYPE(p) == T_ICLASS) {
|
|
rb_ary_push(ary, RBASIC(p)->klass);
|
|
}
|
|
else {
|
|
rb_ary_push(ary, p);
|
|
}
|
|
}
|
|
return ary;
|
|
}
|
|
|
|
static void
|
|
ins_methods_push(st_data_t name, st_data_t ary)
|
|
{
|
|
rb_ary_push((VALUE)ary, ID2SYM((ID)name));
|
|
}
|
|
|
|
static int
|
|
ins_methods_i(st_data_t name, st_data_t type, st_data_t ary)
|
|
{
|
|
switch ((rb_method_visibility_t)type) {
|
|
case METHOD_VISI_UNDEF:
|
|
case METHOD_VISI_PRIVATE:
|
|
break;
|
|
default: /* everything but private */
|
|
ins_methods_push(name, ary);
|
|
break;
|
|
}
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static int
|
|
ins_methods_type_i(st_data_t name, st_data_t type, st_data_t ary, rb_method_visibility_t visi)
|
|
{
|
|
if ((rb_method_visibility_t)type == visi) {
|
|
ins_methods_push(name, ary);
|
|
}
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static int
|
|
ins_methods_prot_i(st_data_t name, st_data_t type, st_data_t ary)
|
|
{
|
|
return ins_methods_type_i(name, type, ary, METHOD_VISI_PROTECTED);
|
|
}
|
|
|
|
static int
|
|
ins_methods_priv_i(st_data_t name, st_data_t type, st_data_t ary)
|
|
{
|
|
return ins_methods_type_i(name, type, ary, METHOD_VISI_PRIVATE);
|
|
}
|
|
|
|
static int
|
|
ins_methods_pub_i(st_data_t name, st_data_t type, st_data_t ary)
|
|
{
|
|
return ins_methods_type_i(name, type, ary, METHOD_VISI_PUBLIC);
|
|
}
|
|
|
|
struct method_entry_arg {
|
|
st_table *list;
|
|
int recur;
|
|
};
|
|
|
|
static enum rb_id_table_iterator_result
|
|
method_entry_i(ID key, VALUE value, void *data)
|
|
{
|
|
const rb_method_entry_t *me = (const rb_method_entry_t *)value;
|
|
struct method_entry_arg *arg = (struct method_entry_arg *)data;
|
|
rb_method_visibility_t type;
|
|
|
|
if (me->def->type == VM_METHOD_TYPE_REFINED) {
|
|
VALUE owner = me->owner;
|
|
me = rb_resolve_refined_method(Qnil, me);
|
|
if (!me) return ID_TABLE_CONTINUE;
|
|
if (!arg->recur && me->owner != owner) return ID_TABLE_CONTINUE;
|
|
}
|
|
if (!st_is_member(arg->list, key)) {
|
|
if (UNDEFINED_METHOD_ENTRY_P(me)) {
|
|
type = METHOD_VISI_UNDEF; /* none */
|
|
}
|
|
else {
|
|
type = METHOD_ENTRY_VISI(me);
|
|
}
|
|
st_add_direct(arg->list, key, (st_data_t)type);
|
|
}
|
|
return ID_TABLE_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
add_instance_method_list(VALUE mod, struct method_entry_arg *me_arg)
|
|
{
|
|
struct rb_id_table *m_tbl = RCLASS_M_TBL(mod);
|
|
if (!m_tbl) return;
|
|
rb_id_table_foreach(m_tbl, method_entry_i, me_arg);
|
|
}
|
|
|
|
static bool
|
|
particular_class_p(VALUE mod)
|
|
{
|
|
if (!mod) return false;
|
|
if (FL_TEST(mod, FL_SINGLETON)) return true;
|
|
if (BUILTIN_TYPE(mod) == T_ICLASS) return true;
|
|
return false;
|
|
}
|
|
|
|
static VALUE
|
|
class_instance_method_list(int argc, const VALUE *argv, VALUE mod, int obj, int (*func) (st_data_t, st_data_t, st_data_t))
|
|
{
|
|
VALUE ary;
|
|
int recur = TRUE, prepended = 0;
|
|
struct method_entry_arg me_arg;
|
|
|
|
if (rb_check_arity(argc, 0, 1)) recur = RTEST(argv[0]);
|
|
|
|
me_arg.list = st_init_numtable();
|
|
me_arg.recur = recur;
|
|
|
|
if (obj) {
|
|
for (; particular_class_p(mod); mod = RCLASS_SUPER(mod)) {
|
|
add_instance_method_list(mod, &me_arg);
|
|
}
|
|
}
|
|
|
|
if (!recur && RCLASS_ORIGIN(mod) != mod) {
|
|
mod = RCLASS_ORIGIN(mod);
|
|
prepended = 1;
|
|
}
|
|
|
|
for (; mod; mod = RCLASS_SUPER(mod)) {
|
|
add_instance_method_list(mod, &me_arg);
|
|
if (BUILTIN_TYPE(mod) == T_ICLASS && !prepended) continue;
|
|
if (!recur) break;
|
|
}
|
|
ary = rb_ary_new2(me_arg.list->num_entries);
|
|
st_foreach(me_arg.list, func, ary);
|
|
st_free_table(me_arg.list);
|
|
|
|
return ary;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* mod.instance_methods(include_super=true) -> array
|
|
*
|
|
* Returns an array containing the names of the public and protected instance
|
|
* methods in the receiver. For a module, these are the public and protected methods;
|
|
* for a class, they are the instance (not singleton) methods. If the optional
|
|
* parameter is <code>false</code>, the methods of any ancestors are not included.
|
|
*
|
|
* module A
|
|
* def method1() end
|
|
* end
|
|
* class B
|
|
* include A
|
|
* def method2() end
|
|
* end
|
|
* class C < B
|
|
* def method3() end
|
|
* end
|
|
*
|
|
* A.instance_methods(false) #=> [:method1]
|
|
* B.instance_methods(false) #=> [:method2]
|
|
* B.instance_methods(true).include?(:method1) #=> true
|
|
* C.instance_methods(false) #=> [:method3]
|
|
* C.instance_methods.include?(:method2) #=> true
|
|
*/
|
|
|
|
VALUE
|
|
rb_class_instance_methods(int argc, const VALUE *argv, VALUE mod)
|
|
{
|
|
return class_instance_method_list(argc, argv, mod, 0, ins_methods_i);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* mod.protected_instance_methods(include_super=true) -> array
|
|
*
|
|
* Returns a list of the protected instance methods defined in
|
|
* <i>mod</i>. If the optional parameter is <code>false</code>, the
|
|
* methods of any ancestors are not included.
|
|
*/
|
|
|
|
VALUE
|
|
rb_class_protected_instance_methods(int argc, const VALUE *argv, VALUE mod)
|
|
{
|
|
return class_instance_method_list(argc, argv, mod, 0, ins_methods_prot_i);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* mod.private_instance_methods(include_super=true) -> array
|
|
*
|
|
* Returns a list of the private instance methods defined in
|
|
* <i>mod</i>. If the optional parameter is <code>false</code>, the
|
|
* methods of any ancestors are not included.
|
|
*
|
|
* module Mod
|
|
* def method1() end
|
|
* private :method1
|
|
* def method2() end
|
|
* end
|
|
* Mod.instance_methods #=> [:method2]
|
|
* Mod.private_instance_methods #=> [:method1]
|
|
*/
|
|
|
|
VALUE
|
|
rb_class_private_instance_methods(int argc, const VALUE *argv, VALUE mod)
|
|
{
|
|
return class_instance_method_list(argc, argv, mod, 0, ins_methods_priv_i);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* mod.public_instance_methods(include_super=true) -> array
|
|
*
|
|
* Returns a list of the public instance methods defined in <i>mod</i>.
|
|
* If the optional parameter is <code>false</code>, the methods of
|
|
* any ancestors are not included.
|
|
*/
|
|
|
|
VALUE
|
|
rb_class_public_instance_methods(int argc, const VALUE *argv, VALUE mod)
|
|
{
|
|
return class_instance_method_list(argc, argv, mod, 0, ins_methods_pub_i);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* obj.methods(regular=true) -> array
|
|
*
|
|
* Returns a list of the names of public and protected methods of
|
|
* <i>obj</i>. This will include all the methods accessible in
|
|
* <i>obj</i>'s ancestors.
|
|
* If the optional parameter is <code>false</code>, it
|
|
* returns an array of <i>obj</i>'s public and protected singleton methods,
|
|
* the array will not include methods in modules included in <i>obj</i>.
|
|
*
|
|
* class Klass
|
|
* def klass_method()
|
|
* end
|
|
* end
|
|
* k = Klass.new
|
|
* k.methods[0..9] #=> [:klass_method, :nil?, :===,
|
|
* # :==~, :!, :eql?
|
|
* # :hash, :<=>, :class, :singleton_class]
|
|
* k.methods.length #=> 56
|
|
*
|
|
* k.methods(false) #=> []
|
|
* def k.singleton_method; end
|
|
* k.methods(false) #=> [:singleton_method]
|
|
*
|
|
* module M123; def m123; end end
|
|
* k.extend M123
|
|
* k.methods(false) #=> [:singleton_method]
|
|
*/
|
|
|
|
VALUE
|
|
rb_obj_methods(int argc, const VALUE *argv, VALUE obj)
|
|
{
|
|
rb_check_arity(argc, 0, 1);
|
|
if (argc > 0 && !RTEST(argv[0])) {
|
|
return rb_obj_singleton_methods(argc, argv, obj);
|
|
}
|
|
return class_instance_method_list(argc, argv, CLASS_OF(obj), 1, ins_methods_i);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* obj.protected_methods(all=true) -> array
|
|
*
|
|
* Returns the list of protected methods accessible to <i>obj</i>. If
|
|
* the <i>all</i> parameter is set to <code>false</code>, only those methods
|
|
* in the receiver will be listed.
|
|
*/
|
|
|
|
VALUE
|
|
rb_obj_protected_methods(int argc, const VALUE *argv, VALUE obj)
|
|
{
|
|
return class_instance_method_list(argc, argv, CLASS_OF(obj), 1, ins_methods_prot_i);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* obj.private_methods(all=true) -> array
|
|
*
|
|
* Returns the list of private methods accessible to <i>obj</i>. If
|
|
* the <i>all</i> parameter is set to <code>false</code>, only those methods
|
|
* in the receiver will be listed.
|
|
*/
|
|
|
|
VALUE
|
|
rb_obj_private_methods(int argc, const VALUE *argv, VALUE obj)
|
|
{
|
|
return class_instance_method_list(argc, argv, CLASS_OF(obj), 1, ins_methods_priv_i);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* obj.public_methods(all=true) -> array
|
|
*
|
|
* Returns the list of public methods accessible to <i>obj</i>. If
|
|
* the <i>all</i> parameter is set to <code>false</code>, only those methods
|
|
* in the receiver will be listed.
|
|
*/
|
|
|
|
VALUE
|
|
rb_obj_public_methods(int argc, const VALUE *argv, VALUE obj)
|
|
{
|
|
return class_instance_method_list(argc, argv, CLASS_OF(obj), 1, ins_methods_pub_i);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* obj.singleton_methods(all=true) -> array
|
|
*
|
|
* Returns an array of the names of singleton methods for <i>obj</i>.
|
|
* If the optional <i>all</i> parameter is true, the list will include
|
|
* methods in modules included in <i>obj</i>.
|
|
* Only public and protected singleton methods are returned.
|
|
*
|
|
* module Other
|
|
* def three() end
|
|
* end
|
|
*
|
|
* class Single
|
|
* def Single.four() end
|
|
* end
|
|
*
|
|
* a = Single.new
|
|
*
|
|
* def a.one()
|
|
* end
|
|
*
|
|
* class << a
|
|
* include Other
|
|
* def two()
|
|
* end
|
|
* end
|
|
*
|
|
* Single.singleton_methods #=> [:four]
|
|
* a.singleton_methods(false) #=> [:two, :one]
|
|
* a.singleton_methods #=> [:two, :one, :three]
|
|
*/
|
|
|
|
VALUE
|
|
rb_obj_singleton_methods(int argc, const VALUE *argv, VALUE obj)
|
|
{
|
|
VALUE ary, klass, origin;
|
|
struct method_entry_arg me_arg;
|
|
struct rb_id_table *mtbl;
|
|
int recur = TRUE;
|
|
|
|
if (rb_check_arity(argc, 0, 1)) recur = RTEST(argv[0]);
|
|
if (RB_TYPE_P(obj, T_CLASS) && FL_TEST(obj, FL_SINGLETON)) {
|
|
rb_singleton_class(obj);
|
|
}
|
|
klass = CLASS_OF(obj);
|
|
origin = RCLASS_ORIGIN(klass);
|
|
me_arg.list = st_init_numtable();
|
|
me_arg.recur = recur;
|
|
if (klass && FL_TEST(klass, FL_SINGLETON)) {
|
|
if ((mtbl = RCLASS_M_TBL(origin)) != 0) rb_id_table_foreach(mtbl, method_entry_i, &me_arg);
|
|
klass = RCLASS_SUPER(klass);
|
|
}
|
|
if (recur) {
|
|
while (klass && (FL_TEST(klass, FL_SINGLETON) || RB_TYPE_P(klass, T_ICLASS))) {
|
|
if (klass != origin && (mtbl = RCLASS_M_TBL(klass)) != 0) rb_id_table_foreach(mtbl, method_entry_i, &me_arg);
|
|
klass = RCLASS_SUPER(klass);
|
|
}
|
|
}
|
|
ary = rb_ary_new2(me_arg.list->num_entries);
|
|
st_foreach(me_arg.list, ins_methods_i, ary);
|
|
st_free_table(me_arg.list);
|
|
|
|
return ary;
|
|
}
|
|
|
|
/*!
|
|
* \}
|
|
*/
|
|
/*!
|
|
* \defgroup defmethod Defining methods
|
|
* There are some APIs to define a method from C.
|
|
* These API takes a C function as a method body.
|
|
*
|
|
* \par Method body functions
|
|
* Method body functions must return a VALUE and
|
|
* can be one of the following form:
|
|
* <dl>
|
|
* <dt>Fixed number of parameters</dt>
|
|
* <dd>
|
|
* This form is a normal C function, excepting it takes
|
|
* a receiver object as the first argument.
|
|
*
|
|
* \code
|
|
* static VALUE my_method(VALUE self, VALUE x, VALUE y);
|
|
* \endcode
|
|
* </dd>
|
|
* <dt>argc and argv style</dt>
|
|
* <dd>
|
|
* This form takes three parameters: \a argc, \a argv and \a self.
|
|
* \a self is the receiver. \a argc is the number of arguments.
|
|
* \a argv is a pointer to an array of the arguments.
|
|
*
|
|
* \code
|
|
* static VALUE my_method(int argc, VALUE *argv, VALUE self);
|
|
* \endcode
|
|
* </dd>
|
|
* <dt>Ruby array style</dt>
|
|
* <dd>
|
|
* This form takes two parameters: self and args.
|
|
* \a self is the receiver. \a args is an Array object which
|
|
* contains the arguments.
|
|
*
|
|
* \code
|
|
* static VALUE my_method(VALUE self, VALUE args);
|
|
* \endcode
|
|
* </dd>
|
|
*
|
|
* \par Number of parameters
|
|
* Method defining APIs takes the number of parameters which the
|
|
* method will takes. This number is called \a argc.
|
|
* \a argc can be:
|
|
* <dl>
|
|
* <dt>zero or positive number</dt>
|
|
* <dd>This means the method body function takes a fixed number of parameters</dd>
|
|
* <dt>-1</dt>
|
|
* <dd>This means the method body function is "argc and argv" style.</dd>
|
|
* <dt>-2</dt>
|
|
* <dd>This means the method body function is "self and args" style.</dd>
|
|
* </dl>
|
|
* \{
|
|
*/
|
|
|
|
#ifdef rb_define_method_id
|
|
#undef rb_define_method_id
|
|
#endif
|
|
void
|
|
rb_define_method_id(VALUE klass, ID mid, VALUE (*func)(ANYARGS), int argc)
|
|
{
|
|
rb_add_method_cfunc(klass, mid, func, argc, METHOD_VISI_PUBLIC);
|
|
}
|
|
|
|
#ifdef rb_define_method
|
|
#undef rb_define_method
|
|
#endif
|
|
void
|
|
rb_define_method(VALUE klass, const char *name, VALUE (*func)(ANYARGS), int argc)
|
|
{
|
|
rb_add_method_cfunc(klass, rb_intern(name), func, argc, METHOD_VISI_PUBLIC);
|
|
}
|
|
|
|
#ifdef rb_define_protected_method
|
|
#undef rb_define_protected_method
|
|
#endif
|
|
void
|
|
rb_define_protected_method(VALUE klass, const char *name, VALUE (*func)(ANYARGS), int argc)
|
|
{
|
|
rb_add_method_cfunc(klass, rb_intern(name), func, argc, METHOD_VISI_PROTECTED);
|
|
}
|
|
|
|
#ifdef rb_define_private_method
|
|
#undef rb_define_private_method
|
|
#endif
|
|
void
|
|
rb_define_private_method(VALUE klass, const char *name, VALUE (*func)(ANYARGS), int argc)
|
|
{
|
|
rb_add_method_cfunc(klass, rb_intern(name), func, argc, METHOD_VISI_PRIVATE);
|
|
}
|
|
|
|
void
|
|
rb_undef_method(VALUE klass, const char *name)
|
|
{
|
|
rb_add_method(klass, rb_intern(name), VM_METHOD_TYPE_UNDEF, 0, METHOD_VISI_UNDEF);
|
|
}
|
|
|
|
static enum rb_id_table_iterator_result
|
|
undef_method_i(ID name, VALUE value, void *data)
|
|
{
|
|
VALUE klass = (VALUE)data;
|
|
rb_add_method(klass, name, VM_METHOD_TYPE_UNDEF, 0, METHOD_VISI_UNDEF);
|
|
return ID_TABLE_CONTINUE;
|
|
}
|
|
|
|
void
|
|
rb_undef_methods_from(VALUE klass, VALUE super)
|
|
{
|
|
struct rb_id_table *mtbl = RCLASS_M_TBL(super);
|
|
if (mtbl) {
|
|
rb_id_table_foreach(mtbl, undef_method_i, (void *)klass);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \}
|
|
*/
|
|
/*!
|
|
* \addtogroup class
|
|
* \{
|
|
*/
|
|
|
|
static inline VALUE
|
|
special_singleton_class_of(VALUE obj)
|
|
{
|
|
switch (obj) {
|
|
case Qnil: return rb_cNilClass;
|
|
case Qfalse: return rb_cFalseClass;
|
|
case Qtrue: return rb_cTrueClass;
|
|
default: return Qnil;
|
|
}
|
|
}
|
|
|
|
VALUE
|
|
rb_special_singleton_class(VALUE obj)
|
|
{
|
|
return special_singleton_class_of(obj);
|
|
}
|
|
|
|
/*!
|
|
* \internal
|
|
* Returns the singleton class of \a obj. Creates it if necessary.
|
|
*
|
|
* \note DO NOT expose the returned singleton class to
|
|
* outside of class.c.
|
|
* Use \ref rb_singleton_class instead for
|
|
* consistency of the metaclass hierarchy.
|
|
*/
|
|
static VALUE
|
|
singleton_class_of(VALUE obj)
|
|
{
|
|
VALUE klass;
|
|
|
|
switch (TYPE(obj)) {
|
|
case T_FIXNUM:
|
|
case T_BIGNUM:
|
|
case T_FLOAT:
|
|
case T_SYMBOL:
|
|
rb_raise(rb_eTypeError, "can't define singleton");
|
|
|
|
case T_FALSE:
|
|
case T_TRUE:
|
|
case T_NIL:
|
|
klass = special_singleton_class_of(obj);
|
|
if (NIL_P(klass))
|
|
rb_bug("unknown immediate %p", (void *)obj);
|
|
return klass;
|
|
|
|
case T_STRING:
|
|
if (FL_TEST_RAW(obj, RSTRING_FSTR)) {
|
|
rb_raise(rb_eTypeError, "can't define singleton");
|
|
}
|
|
}
|
|
|
|
klass = RBASIC(obj)->klass;
|
|
if (!(FL_TEST(klass, FL_SINGLETON) &&
|
|
rb_attr_get(klass, id_attached) == obj)) {
|
|
rb_serial_t serial = RCLASS_SERIAL(klass);
|
|
klass = rb_make_metaclass(obj, klass);
|
|
RCLASS_SERIAL(klass) = serial;
|
|
}
|
|
|
|
RB_FL_SET_RAW(klass, RB_OBJ_FROZEN_RAW(obj));
|
|
|
|
return klass;
|
|
}
|
|
|
|
void
|
|
rb_freeze_singleton_class(VALUE x)
|
|
{
|
|
/* should not propagate to meta-meta-class, and so on */
|
|
if (!(RBASIC(x)->flags & FL_SINGLETON)) {
|
|
VALUE klass = RBASIC_CLASS(x);
|
|
if (klass && (klass = RCLASS_ORIGIN(klass)) != 0 &&
|
|
FL_TEST(klass, (FL_SINGLETON|FL_FREEZE)) == FL_SINGLETON) {
|
|
OBJ_FREEZE_RAW(klass);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Returns the singleton class of \a obj, or nil if obj is not a
|
|
* singleton object.
|
|
*
|
|
* \param obj an arbitrary object.
|
|
* \return the singleton class or nil.
|
|
*/
|
|
VALUE
|
|
rb_singleton_class_get(VALUE obj)
|
|
{
|
|
VALUE klass;
|
|
|
|
if (SPECIAL_CONST_P(obj)) {
|
|
return rb_special_singleton_class(obj);
|
|
}
|
|
klass = RBASIC(obj)->klass;
|
|
if (!FL_TEST(klass, FL_SINGLETON)) return Qnil;
|
|
if (rb_attr_get(klass, id_attached) != obj) return Qnil;
|
|
return klass;
|
|
}
|
|
|
|
/*!
|
|
* Returns the singleton class of \a obj. Creates it if necessary.
|
|
*
|
|
* \param obj an arbitrary object.
|
|
* \throw TypeError if \a obj is an Integer or a Symbol.
|
|
* \return the singleton class.
|
|
*
|
|
* \post \a obj has its own singleton class.
|
|
* \post if \a obj is a class,
|
|
* the returned singleton class also has its own
|
|
* singleton class in order to keep consistency of the
|
|
* inheritance structure of metaclasses.
|
|
* \note a new singleton class will be created
|
|
* if \a obj does not have it.
|
|
* \note the singleton classes for nil, true and false are:
|
|
* NilClass, TrueClass and FalseClass.
|
|
*/
|
|
VALUE
|
|
rb_singleton_class(VALUE obj)
|
|
{
|
|
VALUE klass = singleton_class_of(obj);
|
|
|
|
/* ensures an exposed class belongs to its own eigenclass */
|
|
if (RB_TYPE_P(obj, T_CLASS)) (void)ENSURE_EIGENCLASS(klass);
|
|
|
|
return klass;
|
|
}
|
|
|
|
/*!
|
|
* \}
|
|
*/
|
|
|
|
/*!
|
|
* \addtogroup defmethod
|
|
* \{
|
|
*/
|
|
|
|
#ifdef rb_define_singleton_method
|
|
#undef rb_define_singleton_method
|
|
#endif
|
|
/*!
|
|
* Defines a singleton method for \a obj.
|
|
* \param obj an arbitrary object
|
|
* \param name name of the singleton method
|
|
* \param func the method body
|
|
* \param argc the number of parameters, or -1 or -2. see \ref defmethod.
|
|
*/
|
|
void
|
|
rb_define_singleton_method(VALUE obj, const char *name, VALUE (*func)(ANYARGS), int argc)
|
|
{
|
|
rb_define_method(singleton_class_of(obj), name, func, argc);
|
|
}
|
|
|
|
#ifdef rb_define_module_function
|
|
#undef rb_define_module_function
|
|
#endif
|
|
/*!
|
|
* Defines a module function for \a module.
|
|
* \param module an module or a class.
|
|
* \param name name of the function
|
|
* \param func the method body
|
|
* \param argc the number of parameters, or -1 or -2. see \ref defmethod.
|
|
*/
|
|
void
|
|
rb_define_module_function(VALUE module, const char *name, VALUE (*func)(ANYARGS), int argc)
|
|
{
|
|
rb_define_private_method(module, name, func, argc);
|
|
rb_define_singleton_method(module, name, func, argc);
|
|
}
|
|
|
|
#ifdef rb_define_global_function
|
|
#undef rb_define_global_function
|
|
#endif
|
|
/*!
|
|
* Defines a global function
|
|
* \param name name of the function
|
|
* \param func the method body
|
|
* \param argc the number of parameters, or -1 or -2. see \ref defmethod.
|
|
*/
|
|
void
|
|
rb_define_global_function(const char *name, VALUE (*func)(ANYARGS), int argc)
|
|
{
|
|
rb_define_module_function(rb_mKernel, name, func, argc);
|
|
}
|
|
|
|
|
|
/*!
|
|
* Defines an alias of a method.
|
|
* \param klass the class which the original method belongs to
|
|
* \param name1 a new name for the method
|
|
* \param name2 the original name of the method
|
|
*/
|
|
void
|
|
rb_define_alias(VALUE klass, const char *name1, const char *name2)
|
|
{
|
|
rb_alias(klass, rb_intern(name1), rb_intern(name2));
|
|
}
|
|
|
|
/*!
|
|
* Defines (a) public accessor method(s) for an attribute.
|
|
* \param klass the class which the attribute will belongs to
|
|
* \param name name of the attribute
|
|
* \param read a getter method for the attribute will be defined if \a read is non-zero.
|
|
* \param write a setter method for the attribute will be defined if \a write is non-zero.
|
|
*/
|
|
void
|
|
rb_define_attr(VALUE klass, const char *name, int read, int write)
|
|
{
|
|
rb_attr(klass, rb_intern(name), read, write, FALSE);
|
|
}
|
|
|
|
MJIT_FUNC_EXPORTED VALUE
|
|
rb_keyword_error_new(const char *error, VALUE keys)
|
|
{
|
|
long i = 0, len = RARRAY_LEN(keys);
|
|
VALUE error_message = rb_sprintf("%s keyword%.*s", error, len > 1, "s");
|
|
|
|
if (len > 0) {
|
|
rb_str_cat_cstr(error_message, ": ");
|
|
while (1) {
|
|
const VALUE k = RARRAY_AREF(keys, i);
|
|
rb_str_append(error_message, rb_inspect(k));
|
|
if (++i >= len) break;
|
|
rb_str_cat_cstr(error_message, ", ");
|
|
}
|
|
}
|
|
|
|
return rb_exc_new_str(rb_eArgError, error_message);
|
|
}
|
|
|
|
NORETURN(static void rb_keyword_error(const char *error, VALUE keys));
|
|
static void
|
|
rb_keyword_error(const char *error, VALUE keys)
|
|
{
|
|
rb_exc_raise(rb_keyword_error_new(error, keys));
|
|
}
|
|
|
|
NORETURN(static void unknown_keyword_error(VALUE hash, const ID *table, int keywords));
|
|
static void
|
|
unknown_keyword_error(VALUE hash, const ID *table, int keywords)
|
|
{
|
|
int i;
|
|
for (i = 0; i < keywords; i++) {
|
|
st_data_t key = ID2SYM(table[i]);
|
|
rb_hash_stlike_delete(hash, &key, NULL);
|
|
}
|
|
rb_keyword_error("unknown", rb_hash_keys(hash));
|
|
}
|
|
|
|
|
|
static int
|
|
separate_symbol(st_data_t key, st_data_t value, st_data_t arg)
|
|
{
|
|
VALUE *kwdhash = (VALUE *)arg;
|
|
if (!SYMBOL_P(key)) kwdhash++;
|
|
if (!*kwdhash) *kwdhash = rb_hash_new();
|
|
rb_hash_aset(*kwdhash, (VALUE)key, (VALUE)value);
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
VALUE
|
|
rb_extract_keywords(VALUE *orighash)
|
|
{
|
|
VALUE parthash[2] = {0, 0};
|
|
VALUE hash = *orighash;
|
|
|
|
if (RHASH_EMPTY_P(hash)) {
|
|
*orighash = 0;
|
|
return hash;
|
|
}
|
|
rb_hash_foreach(hash, separate_symbol, (st_data_t)&parthash);
|
|
*orighash = parthash[1];
|
|
if (parthash[1] && RBASIC_CLASS(hash) != rb_cHash) {
|
|
RBASIC_SET_CLASS(parthash[1], RBASIC_CLASS(hash));
|
|
}
|
|
return parthash[0];
|
|
}
|
|
|
|
int
|
|
rb_get_kwargs(VALUE keyword_hash, const ID *table, int required, int optional, VALUE *values)
|
|
{
|
|
int i = 0, j;
|
|
int rest = 0;
|
|
VALUE missing = Qnil;
|
|
st_data_t key;
|
|
|
|
#define extract_kwarg(keyword, val) \
|
|
(key = (st_data_t)(keyword), values ? \
|
|
(rb_hash_stlike_delete(keyword_hash, &key, &(val)) || ((val) = Qundef, 0)) : \
|
|
rb_hash_stlike_lookup(keyword_hash, key, NULL))
|
|
|
|
if (NIL_P(keyword_hash)) keyword_hash = 0;
|
|
|
|
if (optional < 0) {
|
|
rest = 1;
|
|
optional = -1-optional;
|
|
}
|
|
if (required) {
|
|
for (; i < required; i++) {
|
|
VALUE keyword = ID2SYM(table[i]);
|
|
if (keyword_hash) {
|
|
if (extract_kwarg(keyword, values[i])) {
|
|
continue;
|
|
}
|
|
}
|
|
if (NIL_P(missing)) missing = rb_ary_tmp_new(1);
|
|
rb_ary_push(missing, keyword);
|
|
}
|
|
if (!NIL_P(missing)) {
|
|
rb_keyword_error("missing", missing);
|
|
}
|
|
}
|
|
j = i;
|
|
if (optional && keyword_hash) {
|
|
for (i = 0; i < optional; i++) {
|
|
if (extract_kwarg(ID2SYM(table[required+i]), values[required+i])) {
|
|
j++;
|
|
}
|
|
}
|
|
}
|
|
if (!rest && keyword_hash) {
|
|
if (RHASH_SIZE(keyword_hash) > (unsigned int)(values ? 0 : j)) {
|
|
unknown_keyword_error(keyword_hash, table, required+optional);
|
|
}
|
|
}
|
|
if (values && !keyword_hash) {
|
|
for (i = 0; i < required + optional; i++) {
|
|
values[i] = Qundef;
|
|
}
|
|
}
|
|
return j;
|
|
#undef extract_kwarg
|
|
}
|
|
|
|
struct rb_scan_args_t {
|
|
int kw_flag;
|
|
int n_lead;
|
|
int n_opt;
|
|
int n_trail;
|
|
bool f_var;
|
|
bool f_hash;
|
|
bool f_block;
|
|
};
|
|
|
|
static void
|
|
rb_scan_args_parse(int kw_flag, const char *fmt, struct rb_scan_args_t *arg)
|
|
{
|
|
const char *p = fmt;
|
|
|
|
memset(arg, 0, sizeof(*arg));
|
|
arg->kw_flag = kw_flag;
|
|
|
|
if (ISDIGIT(*p)) {
|
|
arg->n_lead = *p - '0';
|
|
p++;
|
|
if (ISDIGIT(*p)) {
|
|
arg->n_opt = *p - '0';
|
|
p++;
|
|
}
|
|
}
|
|
if (*p == '*') {
|
|
arg->f_var = 1;
|
|
p++;
|
|
}
|
|
if (ISDIGIT(*p)) {
|
|
arg->n_trail = *p - '0';
|
|
p++;
|
|
}
|
|
if (*p == ':') {
|
|
arg->f_hash = 1;
|
|
p++;
|
|
}
|
|
if (*p == '&') {
|
|
arg->f_block = 1;
|
|
p++;
|
|
}
|
|
if (*p != '\0') {
|
|
rb_fatal("bad scan arg format: %s", fmt);
|
|
}
|
|
}
|
|
|
|
static int
|
|
rb_scan_args_assign(const struct rb_scan_args_t *arg, int argc, const VALUE *const argv, va_list vargs)
|
|
{
|
|
int i, argi = 0;
|
|
VALUE *var, hash = Qnil;
|
|
#define rb_scan_args_next_param() va_arg(vargs, VALUE *)
|
|
const int kw_flag = arg->kw_flag;
|
|
const int n_lead = arg->n_lead;
|
|
const int n_opt = arg->n_opt;
|
|
const int n_trail = arg->n_trail;
|
|
const int n_mand = n_lead + n_trail;
|
|
const bool f_var = arg->f_var;
|
|
const bool f_hash = arg->f_hash;
|
|
const bool f_block = arg->f_block;
|
|
|
|
/* capture an option hash - phase 1: pop from the argv */
|
|
if (f_hash && argc > 0) {
|
|
VALUE last = argv[argc - 1];
|
|
if (rb_scan_args_keyword_p(kw_flag, last)) {
|
|
hash = rb_hash_dup(last);
|
|
argc--;
|
|
}
|
|
}
|
|
|
|
if (argc < n_mand) {
|
|
goto argc_error;
|
|
}
|
|
|
|
/* capture leading mandatory arguments */
|
|
for (i = 0; i < n_lead; i++) {
|
|
var = rb_scan_args_next_param();
|
|
if (var) *var = argv[argi];
|
|
argi++;
|
|
}
|
|
/* capture optional arguments */
|
|
for (i = 0; i < n_opt; i++) {
|
|
var = rb_scan_args_next_param();
|
|
if (argi < argc - n_trail) {
|
|
if (var) *var = argv[argi];
|
|
argi++;
|
|
}
|
|
else {
|
|
if (var) *var = Qnil;
|
|
}
|
|
}
|
|
/* capture variable length arguments */
|
|
if (f_var) {
|
|
int n_var = argc - argi - n_trail;
|
|
|
|
var = rb_scan_args_next_param();
|
|
if (0 < n_var) {
|
|
if (var) *var = rb_ary_new_from_values(n_var, &argv[argi]);
|
|
argi += n_var;
|
|
}
|
|
else {
|
|
if (var) *var = rb_ary_new();
|
|
}
|
|
}
|
|
/* capture trailing mandatory arguments */
|
|
for (i = 0; i < n_trail; i++) {
|
|
var = rb_scan_args_next_param();
|
|
if (var) *var = argv[argi];
|
|
argi++;
|
|
}
|
|
/* capture an option hash - phase 2: assignment */
|
|
if (f_hash) {
|
|
var = rb_scan_args_next_param();
|
|
if (var) *var = hash;
|
|
}
|
|
/* capture iterator block */
|
|
if (f_block) {
|
|
var = rb_scan_args_next_param();
|
|
if (rb_block_given_p()) {
|
|
*var = rb_block_proc();
|
|
}
|
|
else {
|
|
*var = Qnil;
|
|
}
|
|
}
|
|
|
|
if (argi == argc) {
|
|
return argc;
|
|
}
|
|
|
|
argc_error:
|
|
return -(argc + 1);
|
|
#undef rb_scan_args_next_param
|
|
}
|
|
|
|
static int
|
|
rb_scan_args_result(const struct rb_scan_args_t *const arg, int argc)
|
|
{
|
|
const int n_lead = arg->n_lead;
|
|
const int n_opt = arg->n_opt;
|
|
const int n_trail = arg->n_trail;
|
|
const int n_mand = n_lead + n_trail;
|
|
const bool f_var = arg->f_var;
|
|
|
|
if (argc >= 0) {
|
|
return argc;
|
|
}
|
|
|
|
argc = -argc - 1;
|
|
rb_error_arity(argc, n_mand, f_var ? UNLIMITED_ARGUMENTS : n_mand + n_opt);
|
|
UNREACHABLE_RETURN(-1);
|
|
}
|
|
|
|
#undef rb_scan_args
|
|
int
|
|
rb_scan_args(int argc, const VALUE *argv, const char *fmt, ...)
|
|
{
|
|
va_list vargs;
|
|
struct rb_scan_args_t arg;
|
|
rb_scan_args_parse(RB_SCAN_ARGS_PASS_CALLED_KEYWORDS, fmt, &arg);
|
|
va_start(vargs,fmt);
|
|
argc = rb_scan_args_assign(&arg, argc, argv, vargs);
|
|
va_end(vargs);
|
|
return rb_scan_args_result(&arg, argc);
|
|
}
|
|
|
|
#undef rb_scan_args_kw
|
|
int
|
|
rb_scan_args_kw(int kw_flag, int argc, const VALUE *argv, const char *fmt, ...)
|
|
{
|
|
va_list vargs;
|
|
struct rb_scan_args_t arg;
|
|
rb_scan_args_parse(kw_flag, fmt, &arg);
|
|
va_start(vargs,fmt);
|
|
argc = rb_scan_args_assign(&arg, argc, argv, vargs);
|
|
va_end(vargs);
|
|
return rb_scan_args_result(&arg, argc);
|
|
}
|
|
|
|
int
|
|
rb_class_has_methods(VALUE c)
|
|
{
|
|
return rb_id_table_size(RCLASS_M_TBL(c)) == 0 ? FALSE : TRUE;
|
|
}
|
|
|
|
/*!
|
|
* \}
|
|
*/
|