diff --git a/eval.c b/eval.c index 2cdc88a..0b155a1 100644 --- a/eval.c +++ b/eval.c @@ -1291,6 +1291,68 @@ rb_mod_refinements(VALUE module) return result; } +static int +used_refinements_i(VALUE _, VALUE mod, VALUE ary) +{ + ID id_defined_at; + CONST_ID(id_defined_at, "__defined_at__"); + while(FL_TEST(rb_class_of(mod), RMODULE_IS_REFINEMENT)) { + rb_ary_push(ary, rb_attr_get(rb_class_of(mod), id_defined_at)); + mod = RCLASS_SUPER(mod); + } + return ST_CONTINUE; +} + +/* + * call-seq: + * used_refinements -> array + * + * Returns an array of all active refinements in the current scope. The + * ordering of modules in the resulting array is not defined. + * + * module A + * refine Object do + * end + * end + * + * module B + * refine Object do + * end + * end + * + * module C + * using B + * + * refine Object do + * end + * end + * + * using A + * p used_refinements + * + * C.module_eval { p used_refinements } + * + * produces: + * + * [A] + * [C, B, A] + */ +static VALUE +rb_f_used_refinements(void) +{ + NODE *cref = rb_vm_cref(); + VALUE ary = rb_ary_new(); + + while(cref) { + if(!NIL_P(cref->nd_refinements)) { + rb_hash_foreach(cref->nd_refinements, used_refinements_i, ary); + } + cref = cref->nd_next; + } + + return rb_funcall(ary, rb_intern("uniq"), 0); +} + void rb_obj_call_init(VALUE obj, int argc, VALUE *argv) { @@ -1601,6 +1663,8 @@ Init_eval(void) rb_define_global_function("__method__", rb_f_method_name, 0); rb_define_global_function("__callee__", rb_f_callee_name, 0); rb_define_global_function("__dir__", f_current_dirname, 0); + + rb_define_global_function("used_refinements", rb_f_used_refinements, 0); rb_define_private_method(rb_cModule, "append_features", rb_mod_append_features, 1); rb_define_private_method(rb_cModule, "extend_object", rb_mod_extend_object, 1); diff --git a/test/ruby/test_refinement.rb b/test/ruby/test_refinement.rb index 3ca00ed..f8f03f2 100644 --- a/test/ruby/test_refinement.rb +++ b/test/ruby/test_refinement.rb @@ -822,4 +822,50 @@ class TestRefinement < Test::Unit::TestCase end end end + + module VisibleRefinements + module RefA + refine Object do + def in_ref_a + end + end + end + + module RefB + refine Object do + def in_ref_b + end + end + end + + module RefC + using RefA + + refine Object do + def in_ref_c + end + end + end + + module Foo + using RefB + end + + module Bar + using RefC + end + + module Combined + using Foo + using Bar + end + end + + def test_used_refinements + ref = VisibleRefinements + assert_equal [], used_refinements + assert_equal [ref::RefB], ref::Foo.module_eval { used_refinements } + assert_equal [ref::RefC, ref::RefA], ref::Bar.module_eval { used_refinements } + assert_equal [ref::RefC, ref::RefA, ref::RefB], ref::Combined.module_eval { used_refinements } + end end