summaryrefslogtreecommitdiff
path: root/vm_method.c
diff options
context:
space:
mode:
Diffstat (limited to 'vm_method.c')
-rw-r--r--vm_method.c79
1 files changed, 79 insertions, 0 deletions
diff --git a/vm_method.c b/vm_method.c
index a3d56c8baf..554d209110 100644
--- a/vm_method.c
+++ b/vm_method.c
@@ -1747,6 +1747,84 @@ rb_mod_private(int argc, VALUE *argv, VALUE module)
/*
* call-seq:
+ * ruby2_keywords(method_name, ...) -> self
+ *
+ * For the given method names, marks the method as passing keywords through
+ * a normal argument splat. This should only be called on methods that
+ * accept an argument splat (<tt>*args</tt>) but not explicit keywords or
+ * a keyword splat. It marks the method such that if the method is called
+ * with keyword arguments, the final hash argument is marked with a special
+ * flag such that if it is the final element of a normal argument splat to
+ * another method call, and that method calls does not include explicit
+ * keywords or a keyword splat, the final element is interpreted as keywords.
+ * In other words, keywords will be passed through the method to other
+ * methods.
+ *
+ * This should only be used for methods that delegate keywords to another
+ * method, and only for backwards compatibility with Ruby versions before
+ * 2.7.
+ *
+ * This method will probably be removed at some point, as it exists only
+ * for backwards compatibility, so always check that the module responds
+ * to this method before calling it.
+ *
+ * module Mod
+ * def foo(meth, *args, &block)
+ * send(:"do_#{meth}", *args, &block)
+ * end
+ * ruby2_keywords(:foo) if respond_to?(:ruby2_keywords, true)
+ * end
+ */
+
+static VALUE
+rb_mod_ruby2_keywords(int argc, VALUE *argv, VALUE module)
+{
+ int i;
+ VALUE origin_class = RCLASS_ORIGIN(module);
+
+ rb_check_frozen(module);
+
+ for (i = 0; i < argc; i++) {
+ VALUE v = argv[i];
+ ID name = rb_check_id(&v);
+ rb_method_entry_t *me;
+ VALUE defined_class;
+
+ if (!name) {
+ rb_print_undef_str(module, v);
+ }
+
+ me = search_method(origin_class, name, &defined_class);
+ if (!me && RB_TYPE_P(module, T_MODULE)) {
+ me = search_method(rb_cObject, name, &defined_class);
+ }
+
+ if (UNDEFINED_METHOD_ENTRY_P(me) ||
+ UNDEFINED_REFINED_METHOD_P(me->def)) {
+ rb_print_undef(module, name, METHOD_VISI_UNDEF);
+ }
+
+ if (module == defined_class || origin_class == defined_class) {
+ if (me->def->type == VM_METHOD_TYPE_ISEQ &&
+ me->def->body.iseq.iseqptr->body->param.flags.has_rest &&
+ !me->def->body.iseq.iseqptr->body->param.flags.has_kw &&
+ !me->def->body.iseq.iseqptr->body->param.flags.has_kwrest) {
+ me->def->body.iseq.iseqptr->body->param.flags.ruby2_keywords = 1;
+ rb_clear_method_cache_by_class(module);
+ }
+ else {
+ rb_warn("Skipping set of ruby2_keywords flag for %s (method not defined in Ruby, method accepts keywords, or method does not accept argument splat)", rb_id2name(name));
+ }
+ }
+ else {
+ rb_warn("Skipping set of ruby2_keywords flag for %s (can only set in method defining module)", rb_id2name(name));
+ }
+ }
+ return Qnil;
+}
+
+/*
+ * call-seq:
* mod.public_class_method(symbol, ...) -> mod
* mod.public_class_method(string, ...) -> mod
*
@@ -2127,6 +2205,7 @@ Init_eval_method(void)
rb_define_private_method(rb_cModule, "protected", rb_mod_protected, -1);
rb_define_private_method(rb_cModule, "private", rb_mod_private, -1);
rb_define_private_method(rb_cModule, "module_function", rb_mod_modfunc, -1);
+ rb_define_private_method(rb_cModule, "ruby2_keywords", rb_mod_ruby2_keywords, -1);
rb_define_method(rb_cModule, "method_defined?", rb_mod_method_defined, -1);
rb_define_method(rb_cModule, "public_method_defined?", rb_mod_public_method_defined, -1);