From b6b90bc139e50f6c54bb54786afa9b3a95ac26f5 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 26 Mar 2021 12:13:50 -0700 Subject: [PATCH] Add Module#namespace Given code like this: ```ruby module A module B class C; end class D; end end end ``` We can get from `C` to `B` like `C.namespace`, or to `A` like `C.namespace.namespace`. I want to use this in cases where I don't know the namespace, but I want to find constants that are "siblings" of a constant. For example, I can do `A::B::C.namespace.constants` to find the list of "sibling" constants to `C`. I want to use this feature when walking objects and introspecting. For example: ```ruby ObjectSpace.each_object(Class) do |k| p siblings: k.namespace.constants end ``` --- gc.c | 2 ++ internal/class.h | 2 ++ object.c | 13 +++++++++++++ test/ruby/test_module.rb | 15 +++++++++++++++ variable.c | 27 ++++++++++++--------------- 5 files changed, 44 insertions(+), 15 deletions(-) diff --git a/gc.c b/gc.c index 8218f88d0d..303c12fc87 100644 --- a/gc.c +++ b/gc.c @@ -6383,6 +6383,7 @@ gc_mark_children(rb_objspace_t *objspace, VALUE obj) } if (!RCLASS_EXT(obj)) break; + gc_mark(objspace, RCLASS_EXT(obj)->namespace); mark_m_tbl(objspace, RCLASS_M_TBL(obj)); cc_table_mark(objspace, obj); mark_tbl_no_pin(objspace, RCLASS_IV_TBL(obj)); @@ -9259,6 +9260,7 @@ gc_update_object_references(rb_objspace_t *objspace, VALUE obj) UPDATE_IF_MOVED(objspace, RCLASS(obj)->super); } if (!RCLASS_EXT(obj)) break; + UPDATE_IF_MOVED(objspace, RCLASS_EXT(obj)->namespace); update_m_tbl(objspace, RCLASS_M_TBL(obj)); update_cc_tbl(objspace, obj); diff --git a/internal/class.h b/internal/class.h index 6c03a31a4e..ae8bf1e951 100644 --- a/internal/class.h +++ b/internal/class.h @@ -42,6 +42,8 @@ struct rb_classext_struct { struct rb_id_table *cc_tbl; /* ID -> [[ci, cc1], cc2, ...] */ struct rb_subclass_entry *subclasses; struct rb_subclass_entry **parent_subclasses; + VALUE namespace; + /** * In the case that this is an `ICLASS`, `module_subclasses` points to the link * in the module's `subclasses` list that indicates that the klass has been diff --git a/object.c b/object.c index 8e4c89b974..b808d091f6 100644 --- a/object.c +++ b/object.c @@ -2882,6 +2882,18 @@ rb_mod_const_source_location(int argc, VALUE *argv, VALUE mod) UNREACHABLE_RETURN(Qundef); } +static VALUE +rb_mod_namespace(VALUE mod) +{ + VALUE scope = RCLASS_EXT(mod)->namespace; + if (scope) { + return scope; + } + else { + return Qnil; + } +} + /* * call-seq: * obj.instance_variable_get(symbol) -> obj @@ -4673,6 +4685,7 @@ InitVM_Object(void) rb_define_method(rb_cModule, "const_set", rb_mod_const_set, 2); rb_define_method(rb_cModule, "const_defined?", rb_mod_const_defined, -1); rb_define_method(rb_cModule, "const_source_location", rb_mod_const_source_location, -1); + rb_define_method(rb_cModule, "namespace", rb_mod_namespace, 0); rb_define_private_method(rb_cModule, "remove_const", rb_mod_remove_const, 1); /* in variable.c */ rb_define_method(rb_cModule, "const_missing", diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb index 3cd2f04eff..ec6db10fc6 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -2869,6 +2869,8 @@ def foo; bar; end end ConstLocation = [__FILE__, __LINE__] + class ConstLocationClass; end + module ConstLocationModule; end def test_const_source_location assert_equal(ConstLocation, self.class.const_source_location(:ConstLocation)) @@ -2885,6 +2887,19 @@ def test_const_source_location } end + module Inner; end + + module Outer + X = Inner + end + + def test_namespace + assert_equal ::Object, ::Object.namespace + assert_equal ::Object, ::Kernel.namespace + assert_equal self.class, ConstLocationClass.namespace + assert_equal self.class, Outer::X.namespace + end + module CloneTestM_simple C = 1 def self.m; C; end diff --git a/variable.c b/variable.c index 85ff35ba8c..df64d063a3 100644 --- a/variable.c +++ b/variable.c @@ -2998,8 +2998,6 @@ set_namespace_path(VALUE named_namespace, VALUE namespace_path) void rb_const_set(VALUE klass, ID id, VALUE val) { - rb_const_entry_t *ce; - if (NIL_P(klass)) { rb_raise(rb_eTypeError, "no class/module to define constant %"PRIsVALUE"", QUOTE_ID(id)); @@ -3015,20 +3013,14 @@ rb_const_set(VALUE klass, ID id, VALUE val) { struct rb_id_table *tbl = RCLASS_CONST_TBL(klass); if (!tbl) { - RCLASS_CONST_TBL(klass) = tbl = rb_id_table_create(0); - rb_clear_constant_cache(); - ce = ZALLOC(rb_const_entry_t); - rb_id_table_insert(tbl, id, (VALUE)ce); - setup_const_entry(ce, klass, val, CONST_PUBLIC); - } - else { - struct autoload_const ac = { - .mod = klass, .id = id, - .value = val, .flag = CONST_PUBLIC, - /* fill the rest with 0 */ - }; - const_tbl_update(&ac); + RCLASS_CONST_TBL(klass) = rb_id_table_create(0); } + struct autoload_const ac = { + .mod = klass, .id = id, + .value = val, .flag = CONST_PUBLIC, + /* fill the rest with 0 */ + }; + const_tbl_update(&ac); } RB_VM_LOCK_LEAVE(); @@ -3129,6 +3121,11 @@ const_tbl_update(struct autoload_const *ac) ce = ZALLOC(rb_const_entry_t); rb_id_table_insert(tbl, id, (VALUE)ce); + if (RB_TYPE_P(val, T_MODULE) || RB_TYPE_P(val, T_CLASS)) { + if (!RCLASS_EXT(val)->namespace) { + RB_OBJ_WRITE(val, &RCLASS_EXT(val)->namespace, klass); + } + } setup_const_entry(ce, klass, val, visibility); } } -- 2.30.0