From dc06eee452563efaab2c40f37c7d5276a253d318 Mon Sep 17 00:00:00 2001
From: Jeremy Evans <code@jeremyevans.net>
Date: Tue, 27 Aug 2019 18:38:48 -0700
Subject: [PATCH] Make Proc#parameters support lambda keyword for returning
 parameters as if lambda

This makes it easier to use Proc#parameters to build wrappers.

Implements [Feature #15357]
---
 proc.c                                 | 36 ++++++++++++++++++++++----
 spec/ruby/core/proc/parameters_spec.rb | 11 ++++++++
 test/ruby/test_proc.rb                 | 19 ++++++++++++++
 3 files changed, 61 insertions(+), 5 deletions(-)

diff --git a/proc.c b/proc.c
index 1b8aa3953b..448ef33568 100644
--- a/proc.c
+++ b/proc.c
@@ -1220,18 +1220,44 @@ rb_unnamed_parameters(int arity)
 /*
  * call-seq:
  *    prc.parameters  -> array
+ *    prc.parameters(lambda: true)  -> array
  *
- * Returns the parameter information of this proc.
+ * Returns the parameter information of this proc.  If the lambda
+ * keyword is provided, treats the proc as a lambda if true and
+ * as a non-lambda if false.
  *
+ *    prc = proc{|x, y=42, *other|}
+ *    prc.parameters  #=> [[:opt, :x], [:opt, :y], [:rest, :other]]
  *    prc = lambda{|x, y=42, *other|}
  *    prc.parameters  #=> [[:req, :x], [:opt, :y], [:rest, :other]]
+ *    prc = proc{|x, y=42, *other|}
+ *    prc.parameters(lambda: true)  #=> [[:req, :x], [:opt, :y], [:rest, :other]]
  */
 
 static VALUE
-rb_proc_parameters(VALUE self)
+rb_proc_parameters(int argc, VALUE *argv, VALUE self)
 {
-    int is_proc;
-    const rb_iseq_t *iseq = rb_proc_get_iseq(self, &is_proc);
+    static ID keyword_ids[1];
+    VALUE opt, lambda;
+    VALUE kwargs[1];
+    int is_proc ;
+    const rb_iseq_t *iseq;
+
+    iseq = rb_proc_get_iseq(self, &is_proc);
+
+    if (!keyword_ids[0]) {
+        CONST_ID(keyword_ids[0], "lambda");
+    }
+
+    rb_scan_args(argc, argv, "0:", &opt);
+    if (!NIL_P(opt)) {
+        rb_get_kwargs(opt, keyword_ids, 0, 1, kwargs);
+        lambda = kwargs[0];
+        if (!NIL_P(lambda)) {
+            is_proc = !RTEST(lambda);
+        }
+    }
+
     if (!iseq) {
 	return rb_unnamed_parameters(rb_proc_arity(self));
     }
@@ -3612,7 +3638,7 @@ Init_Proc(void)
     rb_define_method(rb_cProc, "<<", proc_compose_to_left, 1);
     rb_define_method(rb_cProc, ">>", proc_compose_to_right, 1);
     rb_define_method(rb_cProc, "source_location", rb_proc_location, 0);
-    rb_define_method(rb_cProc, "parameters", rb_proc_parameters, 0);
+    rb_define_method(rb_cProc, "parameters", rb_proc_parameters, -1);
 
     /* Exceptions */
     rb_eLocalJumpError = rb_define_class("LocalJumpError", rb_eStandardError);
diff --git a/spec/ruby/core/proc/parameters_spec.rb b/spec/ruby/core/proc/parameters_spec.rb
index 2bc5f1325c..c405706d75 100644
--- a/spec/ruby/core/proc/parameters_spec.rb
+++ b/spec/ruby/core/proc/parameters_spec.rb
@@ -20,6 +20,17 @@
     proc {|x| }.parameters.first.first.should == :opt
   end
 
+  ruby_version_is "2.7" do
+    it "sets the first element of each sub-Array to :req if argument would be required if a lambda if lambda keyword used" do
+      proc {|x| }.parameters(lambda: true).first.first.should == :req
+      proc {|y,*x| }.parameters(lambda: true).first.first.should == :req
+    end
+
+    it "regards named parameters in procs as required if lambda keyword used" do
+      proc {|x| }.parameters(lambda: true).first.first.should == :req
+    end
+  end
+
   it "regards optional keyword parameters in procs as optional" do
     proc {|x: :y| }.parameters.first.first.should == :key
   end
diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb
index fbd3a6c68a..c95c3d29e0 100644
--- a/test/ruby/test_proc.rb
+++ b/test/ruby/test_proc.rb
@@ -1142,6 +1142,25 @@ def test_parameters
     assert_empty(pr.parameters.map{|_,n|n}.compact)
   end
 
+  def test_parameters_lambda
+    assert_equal([], proc {}.parameters(lambda: true))
+    assert_equal([], proc {||}.parameters(lambda: true))
+    assert_equal([[:req, :a]], proc {|a|}.parameters(lambda: true))
+    assert_equal([[:req, :a], [:req, :b]], proc {|a, b|}.parameters(lambda: true))
+    assert_equal([[:opt, :a], [:block, :b]], proc {|a=:a, &b|}.parameters(lambda: true))
+    assert_equal([[:req, :a], [:opt, :b]], proc {|a, b=:b|}.parameters(lambda: true))
+    assert_equal([[:rest, :a]], proc {|*a|}.parameters(lambda: true))
+    assert_equal([[:req, :a], [:rest, :b], [:block, :c]], proc {|a, *b, &c|}.parameters(lambda: true))
+    assert_equal([[:req, :a], [:rest, :b], [:req, :c]], proc {|a, *b, c|}.parameters(lambda: true))
+    assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], proc {|a, *b, c, &d|}.parameters(lambda: true))
+    assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], proc {|a, b=:b, *c, d, &e|}.parameters(lambda: true))
+    assert_equal([[:req], [:block, :b]], proc {|(a), &b|a}.parameters(lambda: true))
+    assert_equal([[:req, :a], [:req, :b], [:opt, :c], [:opt, :d], [:rest, :e], [:req, :f], [:req, :g], [:block, :h]], proc {|a,b,c=:c,d=:d,*e,f,g,&h|}.parameters(lambda: true))
+
+    pr = eval("proc{|"+"(_),"*30+"|}")
+    assert_empty(pr.parameters(lambda: true).map{|_,n|n}.compact)
+  end
+
   def pm0() end
   def pm1(a) end
   def pm2(a, b) end
-- 
2.22.0

