
Issue #19286 has been updated by hmdne (hmdne -). kwargs are... complicated. Let me first extend the issue with additional versions of the above (I run Ruby 3.1, but from what I know, everything applies to anything >= Ruby 3.0): ```ruby [1] pry(main)> method(def a(a:, b:); end).arity => 1 [2] pry(main)> method(def a(a:, b: 5); end).arity => 1 [3] pry(main)> method(def a(a: 5, b: 5); end).arity => -1 [4] pry(main)> method(def a(**kwargs); end).arity => -1 [5] pry(main)> method(def a(**nil); end).arity => 0 [6] pry(main)> ``` So, basically, how I understand the kwargs: they are actually internally the last Hash argument of a function (that is passed with a special flag). If all kwargs are optional, then it's an optional argument (so arity is -1). They are unlike how blocks work - it's not a separate category of arguments. If a function is declared with kwargs arguments special syntax (later I will call it a kwargs-syntax function), then this flag is enforced while checking arity (since Ruby 3.0 I believe). But otherwise, it's not, and kwargs are just passed as a simple argument: ```ruby [8] pry(main)> def a(kwargs); kwargs; end; a(a: 5) => {:a=>5} [9] pry(main)> def a(kwargs = {}); kwargs; end; a(a: 5) => {:a=>5} [10] pry(main)> def a(*args); args; end; a(a: 5) => [{:a=>5}] [11] pry(main)> ``` It is possible to call a kwargs-syntax function with a non-kwargs Hash argument (but only with restarg syntax), if we set a flag (a distinct one, from what I understand) using Hash.ruby2_keywords_hash. ```ruby [15] pry(main)> def a(**kwargs); kwargs; end; a(*[Hash.ruby2_keywords_hash({a: 5})]) => {:a=>5} [16] pry(main)> ``` So, in a way, the first list of examples would be equivalent in terms of arity to the following set of examples: ```ruby [1] pry(main)> method(def a(kwargs); end).arity => 1 [2] pry(main)> method(def a(kwargs); end).arity => 1 [3] pry(main)> method(def a(kwargs = {}); end).arity => -1 [4] pry(main)> method(def a(kwargs = {}); end).arity => -1 [5] pry(main)> method(def a(); end).arity => 0 [6] pry(main)> ``` I think a better interface to get the argument signature of a function is `Method#parameters`: ```ruby [18] pry(main)> method(def a(kwargs); end).parameters => [[:req, :kwargs]] [19] pry(main)> method(def a(**kwargs); end).parameters => [[:keyrest, :kwargs]] [20] pry(main)> method(def a(a:, b:); end).parameters => [[:keyreq, :a], [:keyreq, :b]] [21] pry(main)> method(def a(a: 5, b: 7); end).parameters => [[:key, :a], [:key, :b]] [22] pry(main)> method(def a(kwargs = {}); end).parameters => [[:opt, :kwargs]] [23] pry(main)> method(def a(**nil); end).parameters => [[:nokey]] [24] pry(main)> ``` (My understanding on this topic stems from a fact, that I am working on updating kwargs support in Opal to match MRI behavior) ---------------------------------------- Bug #19286: What should kwargs' arity be? https://bugs.ruby-lang.org/issues/19286#change-100890 * Author: matsuda (Akira Matsuda) * Status: Open * Priority: Normal * ruby -v: ruby 3.3.0dev (2022-12-28T16:43:05Z master cada537040) +YJIT [arm64-darwin21] * Backport: 2.7: UNKNOWN, 3.0: UNKNOWN, 3.1: UNKNOWN, 3.2: UNKNOWN ---------------------------------------- Hello, guys. It's time for a quick Ruby quiz. Q: What is this method's arity? def f(a:, b:) end It requires two arguments, hence it should be 2? Or if we call this method with one argument, the error message says "wrong number of arguments (given 1, expected 0; required keywords: a, b) (ArgumentError)", which means the arity is 0, maybe? A: The answer is, $ all-ruby -e 'p method(def f(a:, b:) end).arity' ruby-2.1.0-preview1 0 ... ruby-2.1.0 0 ruby-2.1.1 -1 ruby-2.1.2 1 ... ruby-3.1.0 1 it's been 1 since 2.1.2. But why 1? Why not 2 nor 0? I asked this question to the ruby-core people, and ko1's answer was that even he has no idea what the number 1 means.  So I thought it'd be worth asking this question here. ---Files-------------------------------- random_-_ruby-lang_-_Slack.png (40.1 KB) -- https://bugs.ruby-lang.org/