diff --git a/lib/delegate.rb b/lib/delegate.rb
index 03ebfddf4a..883ab72456 100644
--- a/lib/delegate.rb
+++ b/lib/delegate.rb
@@ -75,16 +75,16 @@ def initialize(obj)
   #
   # Handles the magic of delegation through \_\_getobj\_\_.
   #
-  def method_missing(m, *args, &block)
+  pass_positional_hash def method_missing(m, *args, **kw, &block)
     r = true
     target = self.__getobj__ {r = false}
 
     if r && target.respond_to?(m)
-      target.__send__(m, *args, &block)
+      target.__send__(m, *args, **kw, &block)
     elsif ::Kernel.method_defined?(m) || ::Kernel.private_method_defined?(m)
       ::Kernel.instance_method(m).bind(self).(*args, &block)
     else
-      super(m, *args, &block)
+      super
     end
   end
 
diff --git a/mjit_compile.c b/mjit_compile.c
index b6ed984b20..ed533dcb40 100644
--- a/mjit_compile.c
+++ b/mjit_compile.c
@@ -217,7 +217,7 @@ compile_inlined_cancel_handler(FILE *f, const struct rb_iseq_constant_body *body
     fprintf(f, "    calling.argc = %d;\n", inline_context->orig_argc);
     fprintf(f, "    calling.recv = reg_cfp->self;\n");
     fprintf(f, "    reg_cfp->self = orig_self;\n");
-    fprintf(f, "    vm_call_iseq_setup_normal(ec, reg_cfp, &calling, (const rb_callable_method_entry_t *)0x%"PRIxVALUE", 0, %d, %d);\n\n",
+    fprintf(f, "    vm_call_iseq_setup_normal(ec, reg_cfp, &calling, (const rb_callable_method_entry_t *)0x%"PRIxVALUE", 0, %d, %d, 0);\n\n",
             inline_context->me, inline_context->param_size, inline_context->local_size); // fastpath_applied_iseq_p checks rb_simple_iseq_p, which ensures has_opt == FALSE
 
     // Start usual cancel from here.
diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb
index 5864ec9588..5a78ffd0ad 100644
--- a/test/ruby/test_keyword.rb
+++ b/test/ruby/test_keyword.rb
@@ -177,6 +177,50 @@ def test_lambda
     assert_equal(["bar", 111111], f[str: "bar", num: 111111])
   end
 
+  def test_pass_positional_hash
+    c = Class.new do
+      pass_positional_hash def foo(x, *args, **kw)
+        send(x ? :bar : :baz, *args, **kw)
+      end
+
+      def bar(*args, **kw)
+        [args, kw]
+      end
+
+      def baz(*args)
+        args
+      end
+    end
+    o = c.new
+
+    assert_equal([[1], {:a=>1}], o.foo(true, 1, :a=>1))
+    assert_equal([1, {:a=>1}], o.foo(false, 1, :a=>1))
+
+    assert_warn(/The last argument is used as the keyword parameter.* for `bar'/m) do
+      assert_equal([[1], {:a=>1}], o.foo(true, 1, {:a=>1}))
+    end
+    assert_equal([1, {:a=>1}], o.foo(false, 1, {:a=>1}))
+
+    assert_warn(/Skipping set of pass positional hash flag for baz \(method not defined in Ruby or method does not accept keyword arguments\)/) do
+      assert_nil(c.send(:pass_positional_hash, :baz))
+    end
+
+    sc = Class.new(c)
+    assert_warn(/Skipping set of pass positional hash flag for bar \(can only set in method defining module\)/) do
+      sc.send(:pass_positional_hash, :bar)
+    end
+    m = Module.new
+    assert_warn(/Skipping set of pass positional hash flag for system \(can only set in method defining module\)/) do
+      m.send(:pass_positional_hash, :system)
+    end
+
+    assert_raise(NameError) { c.send(:pass_positional_hash, "a5e36ccec4f5080a1d5e63f8") }
+    assert_raise(NameError) { c.send(:pass_positional_hash, :quux) }
+
+    c.freeze
+    assert_raise(FrozenError) { c.send(:pass_positional_hash, :baz) }
+  end
+
   def test_regular_kwsplat
     kw = {}
     h = {:a=>1}
diff --git a/test/test_delegate.rb b/test/test_delegate.rb
index 38e38ad781..09064e97f4 100644
--- a/test/test_delegate.rb
+++ b/test/test_delegate.rb
@@ -184,6 +184,25 @@ def delegate_test_private
     end
   end
 
+  def test_keyword_and_hash
+    foo = Object.new
+    def foo.bar(*args)
+      args
+    end
+    def foo.foo(*args, **kw)
+      [args, kw]
+    end
+    d = SimpleDelegator.new(foo)
+    assert_equal([[], {}], d.foo)
+    assert_equal([], d.bar)
+    assert_equal([[], {:a=>1}], d.foo(:a=>1))
+    assert_equal([{:a=>1}], d.bar(:a=>1))
+    assert_warn(/The last argument is used as the keyword parameter.* for `foo'/m) do
+      assert_equal([[], {:a=>1}], d.foo({:a=>1}))
+    end
+    assert_equal([{:a=>1}], d.bar({:a=>1}))
+  end
+
   def test_private_method
     foo = Foo.new
     d = SimpleDelegator.new(foo)
diff --git a/tool/mk_call_iseq_optimized.rb b/tool/mk_call_iseq_optimized.rb
index 9fd84aa696..c092ebeadf 100644
--- a/tool/mk_call_iseq_optimized.rb
+++ b/tool/mk_call_iseq_optimized.rb
@@ -24,7 +24,7 @@ def fname param, local
 #{fname(param, local)}(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc)
 {
     RB_DEBUG_COUNTER_INC(ccf_iseq_fix);
-    return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, #{param}, #{local});
+    return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, #{param}, #{local}, 0);
 }
 
 EOS
diff --git a/tool/ruby_vm/views/_mjit_compile_send.erb b/tool/ruby_vm/views/_mjit_compile_send.erb
index da7e96581b..d81687a433 100644
--- a/tool/ruby_vm/views/_mjit_compile_send.erb
+++ b/tool/ruby_vm/views/_mjit_compile_send.erb
@@ -68,7 +68,7 @@
 % # JIT: Special CALL_METHOD. Bypass cc_copy->call and inline vm_call_iseq_setup_normal for vm_call_iseq_setup_func FASTPATH.
                 fprintf(f, "        {\n");
                 fprintf(f, "            VALUE v;\n");
-                fprintf(f, "            vm_call_iseq_setup_normal(ec, reg_cfp, &calling, (const rb_callable_method_entry_t *)0x%"PRIxVALUE", 0, %d, %d);\n",
+                fprintf(f, "            vm_call_iseq_setup_normal(ec, reg_cfp, &calling, (const rb_callable_method_entry_t *)0x%"PRIxVALUE", 0, %d, %d, 0);\n",
                         (VALUE)cc_copy->me, param_size, iseq->body->local_table_size); // fastpath_applied_iseq_p checks rb_simple_iseq_p, which ensures has_opt == FALSE
                 if (iseq->body->catch_except_p) {
                     fprintf(f, "            VM_ENV_FLAGS_SET(ec->cfp->ep, VM_FRAME_FLAG_FINISH);\n");
diff --git a/vm_args.c b/vm_args.c
index 7ffc756fad..804cd5ee26 100644
--- a/vm_args.c
+++ b/vm_args.c
@@ -642,7 +642,8 @@ static int
 setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * const iseq,
 			 struct rb_calling_info *const calling,
 			 const struct rb_call_info *ci,
-			 VALUE * const locals, const enum arg_setup_type arg_setup_type)
+                         VALUE * const locals, const enum arg_setup_type arg_setup_type,
+                         int *frame_flag)
 {
     const int min_argc = iseq->body->param.lead_num + iseq->body->param.post_num;
     const int max_argc = (iseq->body->param.flags.has_rest == FALSE) ? min_argc + iseq->body->param.opt_num : UNLIMITED_ARGUMENTS;
@@ -682,6 +683,10 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
     args->argv = locals;
     args->rest_dupped = FALSE;
 
+    if (VM_FRAME_PASS_POSITIONAL_HASH_P(ec->cfp)) {
+        kw_flag &= ~VM_CALL_KW_SPLAT;
+    }
+
     if (kw_flag & VM_CALL_KWARG) {
 	args->kw_arg = ((struct rb_call_info_with_kwarg *)ci)->kw_arg;
 
@@ -808,7 +813,14 @@ setup_parameters_complex(rb_execution_context_t * const ec, const rb_iseq_t * co
                  * def foo(k:1) p [k]; end
                  * foo({k:42}) #=> 42
                  */
-                rb_warn_last_hash_to_keyword(calling, ci, iseq);
+                if (iseq->body->param.flags.pass_positional_hash) {
+                    if (frame_flag) {
+                        *frame_flag = VM_FRAME_FLAG_PASS_POSITIONAL_HASH;
+                    }
+                }
+                else {
+                    rb_warn_last_hash_to_keyword(calling, ci, iseq);
+                }
                 given_argc--;
             }
             else if (keyword_hash != Qnil) {
diff --git a/vm_core.h b/vm_core.h
index 52d08fc678..ea77bdbf15 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -358,6 +358,7 @@ struct rb_iseq_constant_body {
 
 	    unsigned int ambiguous_param0 : 1; /* {|a|} */
 	    unsigned int accepts_no_kwarg : 1;
+            unsigned int pass_positional_hash: 1; /* -- Remove In 3.0 -- */
 	} flags;
 
 	unsigned int size;
@@ -1138,11 +1139,11 @@ typedef rb_control_frame_t *
 
 enum {
     /* Frame/Environment flag bits:
-     *   MMMM MMMM MMMM MMMM ____ FFFF FFFF EEEX (LSB)
+     *   MMMM MMMM MMMM MMMM ___F FFFF FFFF EEEX (LSB)
      *
      * X   : tag for GC marking (It seems as Fixnum)
      * EEE : 3 bits Env flags
-     * FF..: 8 bits Frame flags
+     * FF..: 9 bits Frame flags
      * MM..: 15 bits frame magic (to check frame corruption)
      */
 
@@ -1168,6 +1169,7 @@ enum {
     VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM = 0x0200,
     VM_FRAME_FLAG_CFRAME_KW = 0x0400,
     VM_FRAME_FLAG_CFRAME_EMPTY_KW = 0x0800, /* -- Remove In 3.0 -- */
+    VM_FRAME_FLAG_PASS_POSITIONAL_HASH = 0x1000, /* -- Remove In 3.0 -- */
 
     /* env flag */
     VM_ENV_FLAG_LOCAL       = 0x0002,
@@ -1234,6 +1236,11 @@ VM_FRAME_CFRAME_EMPTY_KW_P(const rb_control_frame_t *cfp)
 {
     return VM_ENV_FLAGS(cfp->ep, VM_FRAME_FLAG_CFRAME_EMPTY_KW) != 0;
 }
+static inline int
+VM_FRAME_PASS_POSITIONAL_HASH_P(const rb_control_frame_t *cfp)
+{
+    return VM_ENV_FLAGS(cfp->ep, VM_FRAME_FLAG_PASS_POSITIONAL_HASH) != 0;
+}
 
 static inline int
 VM_FRAME_FINISHED_P(const rb_control_frame_t *cfp)
diff --git a/vm_insnhelper.c b/vm_insnhelper.c
index a83e3a952a..4108c150b2 100644
--- a/vm_insnhelper.c
+++ b/vm_insnhelper.c
@@ -1674,8 +1674,8 @@ vm_base_ptr(const rb_control_frame_t *cfp)
 
 #include "vm_args.c"
 
-static inline VALUE vm_call_iseq_setup_2(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc, int opt_pc, int param_size, int local_size);
-ALWAYS_INLINE(static VALUE vm_call_iseq_setup_normal(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const rb_callable_method_entry_t *me, int opt_pc, int param_size, int local_size));
+static inline VALUE vm_call_iseq_setup_2(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc, int opt_pc, int param_size, int local_size, int frame_flag);
+ALWAYS_INLINE(static VALUE vm_call_iseq_setup_normal(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const rb_callable_method_entry_t *me, int opt_pc, int param_size, int local_size, int frame_flag));
 static inline VALUE vm_call_iseq_setup_tailcall(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc, int opt_pc);
 static VALUE vm_call_super_method(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc);
 static VALUE vm_call_method_nome(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc);
@@ -1700,7 +1700,7 @@ vm_call_iseq_setup_normal_0start(rb_execution_context_t *ec, rb_control_frame_t
     const rb_iseq_t *iseq = def_iseq_ptr(cc->me->def);
     int param = iseq->body->param.size;
     int local = iseq->body->local_table_size;
-    return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, param, local);
+    return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, param, local, 0);
 }
 
 MJIT_STATIC bool
@@ -1822,7 +1822,7 @@ vm_call_iseq_setup_normal_opt_start(rb_execution_context_t *ec, rb_control_frame
     }
 #endif
 
-    return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, opt_pc, param - delta, local);
+    return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, opt_pc, param - delta, local, 0);
 }
 
 static void
@@ -1852,7 +1852,7 @@ vm_call_iseq_setup_kwparm_kwarg(rb_execution_context_t *ec, rb_control_frame_t *
 
     int param = iseq->body->param.size;
     int local = iseq->body->local_table_size;
-    return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, param, local);
+    return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, param, local, 0);
 }
 
 static VALUE
@@ -1877,12 +1877,12 @@ vm_call_iseq_setup_kwparm_nokwarg(rb_execution_context_t *ec, rb_control_frame_t
 
     int param = iseq->body->param.size;
     int local = iseq->body->local_table_size;
-    return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, param, local);
+    return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, param, local, 0);
 }
 
 static inline int
 vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc,
-		    const rb_iseq_t *iseq, VALUE *argv, int param_size, int local_size)
+                    const rb_iseq_t *iseq, VALUE *argv, int param_size, int local_size, int *frame_flag)
 {
     if (LIKELY(!(ci->flag & VM_CALL_KW_SPLAT))) {
         if (LIKELY(rb_simple_iseq_p(iseq))) {
@@ -1959,7 +1959,7 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling,
         }
     }
 
-    return setup_parameters_complex(ec, iseq, calling, ci, argv, arg_setup_method);
+    return setup_parameters_complex(ec, iseq, calling, ci, argv, arg_setup_method, frame_flag);
 }
 
 static VALUE
@@ -1970,16 +1970,17 @@ vm_call_iseq_setup(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct r
     const rb_iseq_t *iseq = def_iseq_ptr(cc->me->def);
     const int param_size = iseq->body->param.size;
     const int local_size = iseq->body->local_table_size;
-    const int opt_pc = vm_callee_setup_arg(ec, calling, ci, cc, def_iseq_ptr(cc->me->def), cfp->sp - calling->argc, param_size, local_size);
-    return vm_call_iseq_setup_2(ec, cfp, calling, ci, cc, opt_pc, param_size, local_size);
+    int frame_flag = 0;
+    const int opt_pc = vm_callee_setup_arg(ec, calling, ci, cc, def_iseq_ptr(cc->me->def), cfp->sp - calling->argc, param_size, local_size, &frame_flag);
+    return vm_call_iseq_setup_2(ec, cfp, calling, ci, cc, opt_pc, param_size, local_size, frame_flag);
 }
 
 static inline VALUE
 vm_call_iseq_setup_2(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc,
-		     int opt_pc, int param_size, int local_size)
+                     int opt_pc, int param_size, int local_size, int frame_flag)
 {
     if (LIKELY(!(ci->flag & VM_CALL_TAILCALL))) {
-        return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, opt_pc, param_size, local_size);
+        return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, opt_pc, param_size, local_size, frame_flag);
     }
     else {
 	return vm_call_iseq_setup_tailcall(ec, cfp, calling, ci, cc, opt_pc);
@@ -1988,14 +1989,14 @@ vm_call_iseq_setup_2(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct
 
 static inline VALUE
 vm_call_iseq_setup_normal(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const rb_callable_method_entry_t *me,
-                          int opt_pc, int param_size, int local_size)
+                          int opt_pc, int param_size, int local_size, int frame_flag)
 {
     const rb_iseq_t *iseq = def_iseq_ptr(me->def);
     VALUE *argv = cfp->sp - calling->argc;
     VALUE *sp = argv + param_size;
     cfp->sp = argv - 1 /* recv */;
 
-    vm_push_frame(ec, iseq, VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL, calling->recv,
+    vm_push_frame(ec, iseq, frame_flag | VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL, calling->recv,
                   calling->block_handler, (VALUE)me,
                   iseq->body->iseq_encoded + opt_pc, sp,
                   local_size - param_size,
@@ -2992,7 +2993,7 @@ vm_callee_setup_block_arg(rb_execution_context_t *ec, struct rb_calling_info *ca
 	return 0;
     }
     else {
-	return setup_parameters_complex(ec, iseq, calling, ci, argv, arg_setup_type);
+        return setup_parameters_complex(ec, iseq, calling, ci, argv, arg_setup_type, 0);
     }
 }
 
diff --git a/vm_method.c b/vm_method.c
index 7421bc96c2..aacb69bd66 100644
--- a/vm_method.c
+++ b/vm_method.c
@@ -1745,6 +1745,75 @@ rb_mod_private(int argc, VALUE *argv, VALUE module)
     return set_visibility(argc, argv, module, METHOD_VISI_PRIVATE);
 }
 
+/*
+ *  call-seq:
+ *     pass_positional_hash(method_name, ...)    -> self
+ *
+ *  For the given method names, marks each the method as not issuing a warning
+ *  if converting a positional hash to a keyword argument, and automatically
+ *  converts keyword splats inside the method into positional arguments if
+ *  the method was called with positional hash that was converted to a keyword
+ *  argument.
+ *
+ *  This should only be used for methods that delegate keywords to another
+ *  method, suppressing a warning when the target method does not accept
+ *  keyword arguments the final argument is a hash.
+ *
+ *  Will only be present in 2.7, will be removed in 3.0, so always check that
+ *  the module responds to this method before calling it.
+ *
+ *    module Mod
+ *      def foo(meth, *args, **kw, &block)
+ *        send(:"do_#{meth}", *args, **kw, &block)
+ *      end
+ *      pass_positional_hash(:foo) if respond_to?(:pass_positional_hash, false)
+ *    end
+ */
+
+static VALUE
+rb_mod_pass_positional_hash(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_kwrest) {
+                me->def->body.iseq.iseqptr->body->param.flags.pass_positional_hash = 1;
+                rb_clear_method_cache_by_class(module);
+            }
+            else {
+                rb_warn("Skipping set of pass positional hash flag for %s (method not defined in Ruby or method does not accept keyword arguments)", rb_id2name(name));
+            }
+        }
+        else {
+            rb_warn("Skipping set of pass positional hash flag for %s (can only set in method defining module)", rb_id2name(name));
+        }
+    }
+    return Qnil;
+}
+
 /*
  *  call-seq:
  *     mod.public_class_method(symbol, ...)    -> mod
@@ -2127,6 +2196,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, "pass_positional_hash", rb_mod_pass_positional_hash, -1); /* -- Remove In 3.0 -- */
 
     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);
