mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	Deprecate include/prepend in refinements and add Refinement#import_methods instead
Refinement#import_methods imports methods from modules. Unlike Module#include, it copies methods and adds them into the refinement, so the refinement is activated in the imported methods. [Bug #17429] [ruby-core:101639]
This commit is contained in:
		
							parent
							
								
									7185c00fcc
								
							
						
					
					
						commit
						6606597109
					
				
					 8 changed files with 185 additions and 4 deletions
				
			
		
							
								
								
									
										6
									
								
								NEWS.md
									
										
									
									
									
								
							
							
						
						
									
										6
									
								
								NEWS.md
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -171,6 +171,12 @@ Outstanding ones only.
 | 
			
		|||
 | 
			
		||||
    * Replace copy coroutine with pthread implementation. [[Feature #18015]]
 | 
			
		||||
 | 
			
		||||
* Refinement
 | 
			
		||||
 | 
			
		||||
    * New class which represents a module created by Module#refine.
 | 
			
		||||
      `include` and `prepend` are deprecated, and `import_methods` is added
 | 
			
		||||
      instead. [[Bug #17429]]
 | 
			
		||||
 | 
			
		||||
## Stdlib updates
 | 
			
		||||
 | 
			
		||||
* The following default gem are updated.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										20
									
								
								class.c
									
										
									
									
									
								
							
							
						
						
									
										20
									
								
								class.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -681,11 +681,13 @@ Init_class_hierarchy(void)
 | 
			
		|||
 | 
			
		||||
    rb_cModule = boot_defclass("Module", rb_cObject);
 | 
			
		||||
    rb_cClass =  boot_defclass("Class",  rb_cModule);
 | 
			
		||||
    rb_cRefinement =  boot_defclass("Refinement",  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_cRefinement, rb_cClass);
 | 
			
		||||
    RBASIC_SET_CLASS(rb_cBasicObject, rb_cClass);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -824,12 +826,24 @@ rb_module_s_alloc(VALUE klass)
 | 
			
		|||
    return mod;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline VALUE
 | 
			
		||||
module_new(VALUE klass)
 | 
			
		||||
{
 | 
			
		||||
    VALUE mdl = class_alloc(T_MODULE, klass);
 | 
			
		||||
    RCLASS_M_TBL_INIT(mdl);
 | 
			
		||||
    return (VALUE)mdl;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VALUE
 | 
			
		||||
rb_module_new(void)
 | 
			
		||||
{
 | 
			
		||||
    VALUE mdl = class_alloc(T_MODULE, rb_cModule);
 | 
			
		||||
    RCLASS_M_TBL_INIT(mdl);
 | 
			
		||||
    return (VALUE)mdl;
 | 
			
		||||
    return module_new(rb_cModule);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VALUE
 | 
			
		||||
rb_refinement_new(void)
 | 
			
		||||
{
 | 
			
		||||
    return module_new(rb_cRefinement);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Kept for compatibility. Use rb_module_new() instead.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										68
									
								
								eval.c
									
										
									
									
									
								
							
							
						
						
									
										68
									
								
								eval.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1128,6 +1128,10 @@ rb_mod_include(int argc, VALUE *argv, VALUE module)
 | 
			
		|||
    CONST_ID(id_append_features, "append_features");
 | 
			
		||||
    CONST_ID(id_included, "included");
 | 
			
		||||
 | 
			
		||||
    if (FL_TEST(module, RMODULE_IS_REFINEMENT)) {
 | 
			
		||||
        rb_warn_deprecated_to_remove_at(3.2, "Refinement#include", NULL);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
 | 
			
		||||
    for (i = 0; i < argc; i++)
 | 
			
		||||
	Check_Type(argv[i], T_MODULE);
 | 
			
		||||
| 
						 | 
				
			
			@ -1174,6 +1178,10 @@ rb_mod_prepend(int argc, VALUE *argv, VALUE module)
 | 
			
		|||
    int i;
 | 
			
		||||
    ID id_prepend_features, id_prepended;
 | 
			
		||||
 | 
			
		||||
    if (FL_TEST(module, RMODULE_IS_REFINEMENT)) {
 | 
			
		||||
        rb_warn_deprecated_to_remove_at(3.2, "Refinement#prepend", NULL);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    CONST_ID(id_prepend_features, "prepend_features");
 | 
			
		||||
    CONST_ID(id_prepended, "prepended");
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1397,7 +1405,7 @@ rb_mod_refine(VALUE module, VALUE klass)
 | 
			
		|||
    refinement = rb_hash_lookup(refinements, klass);
 | 
			
		||||
    if (NIL_P(refinement)) {
 | 
			
		||||
	VALUE superclass = refinement_superclass(klass);
 | 
			
		||||
	refinement = rb_module_new();
 | 
			
		||||
	refinement = rb_refinement_new();
 | 
			
		||||
	RCLASS_SET_SUPER(refinement, superclass);
 | 
			
		||||
	FL_SET(refinement, RMODULE_IS_REFINEMENT);
 | 
			
		||||
	CONST_ID(id_refined_class, "__refined_class__");
 | 
			
		||||
| 
						 | 
				
			
			@ -1502,6 +1510,63 @@ rb_mod_s_used_modules(VALUE _)
 | 
			
		|||
    return rb_funcall(ary, rb_intern("uniq"), 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct refinement_import_methods_arg {
 | 
			
		||||
    rb_cref_t *cref;
 | 
			
		||||
    VALUE refinement;
 | 
			
		||||
    VALUE module;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* vm.c */
 | 
			
		||||
rb_cref_t *rb_vm_cref_dup_without_refinements(const rb_cref_t *cref);
 | 
			
		||||
 | 
			
		||||
static enum rb_id_table_iterator_result
 | 
			
		||||
refinement_import_methods_i(ID key, VALUE value, void *data)
 | 
			
		||||
{
 | 
			
		||||
    const rb_method_entry_t *me = (const rb_method_entry_t *)value;
 | 
			
		||||
    struct refinement_import_methods_arg *arg = (struct refinement_import_methods_arg *)data;
 | 
			
		||||
 | 
			
		||||
    if (me->def->type != VM_METHOD_TYPE_ISEQ) {
 | 
			
		||||
        rb_raise(rb_eArgError, "Can't import method: %"PRIsVALUE"#%"PRIsVALUE, rb_class_path(arg->module), rb_id2str(key));
 | 
			
		||||
    }
 | 
			
		||||
    rb_cref_t *new_cref = rb_vm_cref_dup_without_refinements(me->def->body.iseq.cref);
 | 
			
		||||
    CREF_REFINEMENTS_SET(new_cref, CREF_REFINEMENTS(arg->cref));
 | 
			
		||||
    rb_add_method_iseq(arg->refinement, key, me->def->body.iseq.iseqptr, new_cref, METHOD_ENTRY_VISI(me));
 | 
			
		||||
    return ID_TABLE_CONTINUE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 *  call-seq:
 | 
			
		||||
 *     import_methods(module, ...)    -> self
 | 
			
		||||
 *
 | 
			
		||||
 *  Imports methods from modules. Unlike Module#include,
 | 
			
		||||
 *  Refinement#import_methods copies methods and adds them into the refinement,
 | 
			
		||||
 *  so the refinement is activated in the imported methods.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
static VALUE
 | 
			
		||||
refinement_import_methods(int argc, VALUE *argv, VALUE refinement)
 | 
			
		||||
{
 | 
			
		||||
    int i;
 | 
			
		||||
    struct refinement_import_methods_arg arg;
 | 
			
		||||
 | 
			
		||||
    rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
 | 
			
		||||
    for (i = 0; i < argc; i++) {
 | 
			
		||||
        Check_Type(argv[i], T_MODULE);
 | 
			
		||||
        if (RCLASS_SUPER(argv[i])) {
 | 
			
		||||
            rb_warn("%"PRIsVALUE" has ancestors, but Refinement#import_methods doesn't import their methods", rb_class_path(argv[i]));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    arg.cref = rb_vm_cref_replace_with_duplicated_cref();
 | 
			
		||||
    arg.refinement = refinement;
 | 
			
		||||
    for (i = 0; i < argc; i++) {
 | 
			
		||||
        arg.module = argv[i];
 | 
			
		||||
        struct rb_id_table *m_tbl = RCLASS_M_TBL(argv[i]);
 | 
			
		||||
        if (!m_tbl) continue;
 | 
			
		||||
        rb_id_table_foreach(m_tbl, refinement_import_methods_i, &arg);
 | 
			
		||||
    }
 | 
			
		||||
    return refinement;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
rb_obj_call_init(VALUE obj, int argc, const VALUE *argv)
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -1885,6 +1950,7 @@ Init_eval(void)
 | 
			
		|||
    rb_define_singleton_method(rb_cModule, "used_modules",
 | 
			
		||||
			       rb_mod_s_used_modules, 0);
 | 
			
		||||
    rb_undef_method(rb_cClass, "refine");
 | 
			
		||||
    rb_define_private_method(rb_cRefinement, "import_methods", refinement_import_methods, -1);
 | 
			
		||||
 | 
			
		||||
    rb_undef_method(rb_cClass, "module_function");
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -82,6 +82,7 @@ RUBY_EXTERN VALUE rb_cInteger;                /**< `Module` class. */
 | 
			
		|||
RUBY_EXTERN VALUE rb_cMatch;                  /**< `MatchData` class. */
 | 
			
		||||
RUBY_EXTERN VALUE rb_cMethod;                 /**< `Method` class. */
 | 
			
		||||
RUBY_EXTERN VALUE rb_cModule;                 /**< `Module` class. */
 | 
			
		||||
RUBY_EXTERN VALUE rb_cRefinement;             /**< `Refinement` class. */
 | 
			
		||||
RUBY_EXTERN VALUE rb_cNameErrorMesg;          /**< `NameError::Message` class. */
 | 
			
		||||
RUBY_EXTERN VALUE rb_cNilClass;               /**< `NilClass` class. */
 | 
			
		||||
RUBY_EXTERN VALUE rb_cNumeric;                /**< `Numeric` class. */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -100,6 +100,14 @@ VALUE rb_define_class_id_under(VALUE outer, ID id, VALUE super);
 | 
			
		|||
 */
 | 
			
		||||
VALUE rb_module_new(void);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Creates a new, anonymous refinement.
 | 
			
		||||
 *
 | 
			
		||||
 * @return An anonymous refinement.
 | 
			
		||||
 */
 | 
			
		||||
VALUE rb_refinement_new(void);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This is a very badly designed API that creates an anonymous module.
 | 
			
		||||
 *
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								object.c
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								object.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -50,6 +50,7 @@ VALUE rb_mKernel;
 | 
			
		|||
VALUE rb_cObject;
 | 
			
		||||
VALUE rb_cModule;
 | 
			
		||||
VALUE rb_cClass;
 | 
			
		||||
VALUE rb_cRefinement;
 | 
			
		||||
 | 
			
		||||
VALUE rb_cNilClass;
 | 
			
		||||
VALUE rb_cTrueClass;
 | 
			
		||||
| 
						 | 
				
			
			@ -4357,6 +4358,7 @@ InitVM_Object(void)
 | 
			
		|||
    rb_cObject = rb_define_class("Object", rb_cBasicObject);
 | 
			
		||||
    rb_cModule = rb_define_class("Module", rb_cObject);
 | 
			
		||||
    rb_cClass =  rb_define_class("Class",  rb_cModule);
 | 
			
		||||
    rb_cRefinement = rb_define_class("Refinement", rb_cModule);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    rb_define_private_method(rb_cBasicObject, "initialize", rb_obj_initialize, 0);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2590,6 +2590,71 @@ class TestRefinement < Test::Unit::TestCase
 | 
			
		|||
    assert_equal([refinement], as, "[ruby-core:86949] [Bug #14744]")
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  module TestImport
 | 
			
		||||
    class A
 | 
			
		||||
      def foo
 | 
			
		||||
        "original"
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    module B
 | 
			
		||||
      BAR = "bar"
 | 
			
		||||
 | 
			
		||||
      def bar
 | 
			
		||||
        "#{foo}:#{BAR}"
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    module C
 | 
			
		||||
      refine A do
 | 
			
		||||
        import_methods B
 | 
			
		||||
 | 
			
		||||
        def foo
 | 
			
		||||
          "refined"
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    module D
 | 
			
		||||
      refine A do
 | 
			
		||||
        include B
 | 
			
		||||
 | 
			
		||||
        def foo
 | 
			
		||||
          "refined"
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    module UsingC
 | 
			
		||||
      using C
 | 
			
		||||
 | 
			
		||||
      def self.call_bar
 | 
			
		||||
        A.new.bar
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    module UsingD
 | 
			
		||||
      using D
 | 
			
		||||
 | 
			
		||||
      def self.call_bar
 | 
			
		||||
        A.new.bar
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_import_methods
 | 
			
		||||
    assert_equal("refined:bar", TestImport::UsingC.call_bar)
 | 
			
		||||
    assert_equal("original:bar", TestImport::UsingD.call_bar)
 | 
			
		||||
 | 
			
		||||
    assert_raise(ArgumentError) do
 | 
			
		||||
      Module.new do
 | 
			
		||||
        refine Integer do
 | 
			
		||||
          import_methods Enumerable
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
 | 
			
		||||
  def eval_using(mod, s)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										19
									
								
								vm.c
									
										
									
									
									
								
							
							
						
						
									
										19
									
								
								vm.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -294,6 +294,25 @@ vm_cref_dup(const rb_cref_t *cref)
 | 
			
		|||
    return new_cref;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
rb_cref_t *
 | 
			
		||||
rb_vm_cref_dup_without_refinements(const rb_cref_t *cref)
 | 
			
		||||
{
 | 
			
		||||
    VALUE klass = CREF_CLASS(cref);
 | 
			
		||||
    const rb_scope_visibility_t *visi = CREF_SCOPE_VISI(cref);
 | 
			
		||||
    rb_cref_t *next_cref = CREF_NEXT(cref), *new_cref;
 | 
			
		||||
    int pushed_by_eval = CREF_PUSHED_BY_EVAL(cref);
 | 
			
		||||
 | 
			
		||||
    new_cref = vm_cref_new(klass, visi->method_visi, visi->module_func, next_cref, pushed_by_eval);
 | 
			
		||||
 | 
			
		||||
    if (!NIL_P(CREF_REFINEMENTS(cref))) {
 | 
			
		||||
        CREF_REFINEMENTS_SET(new_cref, Qnil);
 | 
			
		||||
        CREF_OMOD_SHARED_UNSET(new_cref);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return new_cref;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static rb_cref_t *
 | 
			
		||||
vm_cref_new_toplevel(rb_execution_context_t *ec)
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue