ruby-changes:69305
From: Shugo <ko1@a...>
Date: Thu, 21 Oct 2021 16:36:17 +0900 (JST)
Subject: [ruby-changes:69305] 6606597109 (master): Deprecate include/prepend in refinements and add Refinement#import_methods instead
https://git.ruby-lang.org/ruby.git/commit/?id=6606597109 From 6606597109bdb535a150606323ce3d8f5750e1f6 Mon Sep 17 00:00:00 2001 From: Shugo Maeda <shugo@r...> Date: Thu, 21 Oct 2021 16:21:08 +0900 Subject: 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] --- NEWS.md | 6 ++++ class.c | 20 +++++++++-- eval.c | 68 +++++++++++++++++++++++++++++++++++- include/ruby/internal/globals.h | 1 + include/ruby/internal/intern/class.h | 8 +++++ object.c | 2 ++ test/ruby/test_refinement.rb | 65 ++++++++++++++++++++++++++++++++++ vm.c | 19 ++++++++++ 8 files changed, 185 insertions(+), 4 deletions(-) diff --git a/NEWS.md b/NEWS.md index 305231e542..6ccfd90971 100644 --- a/NEWS.md +++ b/NEWS.md @@ -171,6 +171,12 @@ Outstanding ones only. https://github.com/ruby/ruby/blob/trunk/NEWS.md#L171 * 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. diff --git a/class.c b/class.c index ca8bdc2e79..5bfbe6445a 100644 --- a/class.c +++ b/class.c @@ -681,11 +681,13 @@ Init_class_hierarchy(void) https://github.com/ruby/ruby/blob/trunk/class.c#L681 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,14 +826,26 @@ rb_module_s_alloc(VALUE klass) https://github.com/ruby/ruby/blob/trunk/class.c#L826 return mod; } -VALUE -rb_module_new(void) +static inline VALUE +module_new(VALUE klass) { - VALUE mdl = class_alloc(T_MODULE, rb_cModule); + VALUE mdl = class_alloc(T_MODULE, klass); RCLASS_M_TBL_INIT(mdl); return (VALUE)mdl; } +VALUE +rb_module_new(void) +{ + return module_new(rb_cModule); +} + +VALUE +rb_refinement_new(void) +{ + return module_new(rb_cRefinement); +} + // Kept for compatibility. Use rb_module_new() instead. VALUE rb_define_module_id(ID id) diff --git a/eval.c b/eval.c index 880c63b88f..1cc699445f 100644 --- a/eval.c +++ b/eval.c @@ -1128,6 +1128,10 @@ rb_mod_include(int argc, VALUE *argv, VALUE module) https://github.com/ruby/ruby/blob/trunk/eval.c#L1128 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) https://github.com/ruby/ruby/blob/trunk/eval.c#L1178 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) https://github.com/ruby/ruby/blob/trunk/eval.c#L1405 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 _) https://github.com/ruby/ruby/blob/trunk/eval.c#L1510 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) https://github.com/ruby/ruby/blob/trunk/eval.c#L1950 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"); diff --git a/include/ruby/internal/globals.h b/include/ruby/internal/globals.h index b478e30b04..5a414fc472 100644 --- a/include/ruby/internal/globals.h +++ b/include/ruby/internal/globals.h @@ -82,6 +82,7 @@ RUBY_EXTERN VALUE rb_cInteger; /**< `Module` class. */ https://github.com/ruby/ruby/blob/trunk/include/ruby/internal/globals.h#L82 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. */ diff --git a/include/ruby/internal/intern/class.h b/include/ruby/internal/intern/class.h index af0c0768b8..60baf98472 100644 --- a/include/ruby/internal/intern/class.h +++ b/include/ruby/internal/intern/class.h @@ -100,6 +100,14 @@ VALUE rb_define_class_id_under(VALUE outer, ID id, VALUE super); https://github.com/ruby/ruby/blob/trunk/include/ruby/internal/intern/class.h#L100 */ 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. * diff --git a/object.c b/object.c index 4dfa7a44f8..5eca02a08c 100644 --- a/object.c +++ b/object.c @@ -50,6 +50,7 @@ VALUE rb_mKernel; https://github.com/ruby/ruby/blob/trunk/object.c#L50 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) https://github.com/ruby/ruby/blob/trunk/object.c#L4358 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); diff --git a/test/ruby/test_refinement.rb b/test/ruby/test_refinement.rb index 96baab03ee..fa9f5532d5 100644 --- a/test/ruby/test_refinement.rb +++ b/test/ruby/test_refinement.rb @@ -2590,6 +2590,71 @@ class TestRefinement < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_refinement.rb#L2590 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 + e (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/