[前][次][番号順一覧][スレッド一覧]

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/

[前][次][番号順一覧][スレッド一覧]