https://redmine.ruby-lang.org/https://redmine.ruby-lang.org/favicon.ico?17113305112018-07-12T11:21:05ZRuby Issue Tracking SystemRuby master - Bug #14909: Method call with object that has to_hash method crashes (method with splat and keyword arguments)https://redmine.ruby-lang.org/issues/14909?journal_id=729352018-07-12T11:21:05Zmame (Yusuke Endoh)mame@ruby-lang.org
<ul><li><strong>Status</strong> changed from <i>Open</i> to <i>Feedback</i></li></ul><p>In the plan of Ruby 3 (<a class="issue tracker-2 status-5 priority-4 priority-default closed" title="Feature: "Real" keyword argument (Closed)" href="https://redmine.ruby-lang.org/issues/14183">#14183</a>), keyword arguments will be separated from other kinds of arguments. If this plan is implemented successfully, you can use <code>my_func(Test.new)</code> and <code>my_func(**Test.new)</code> for each purpose. If you call <code>my_func(Test.new)</code>, the argument will be passed as a part of the rest parameter <code>objects</code>. If you call <code>my_func(**Test.new)</code>, the argument will be handled as a keyword parameter.</p>
<p>So, I'd like to propose keeping the current behavior as is, because changing the semantics will bring extra complexity. Instead, just wait for Ruby 3.</p> Ruby master - Bug #14909: Method call with object that has to_hash method crashes (method with splat and keyword arguments)https://redmine.ruby-lang.org/issues/14909?journal_id=729362018-07-12T12:29:21ZEregon (Benoit Daloze)
<ul></ul><p>At least, it behaves the same if passing a keyword arguments directly:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">def</span> <span class="nf">my_func</span><span class="p">(</span><span class="o">*</span><span class="n">objects</span><span class="p">,</span> <span class="ss">error_code: </span><span class="mi">400</span><span class="p">)</span>
<span class="n">objects</span><span class="p">.</span><span class="nf">inspect</span>
<span class="k">end</span>
<span class="n">my_func</span><span class="p">(</span><span class="ss">to_hash_key: </span><span class="s2">"to_hash"</span><span class="p">)</span> <span class="c1"># => unknown keyword: to_hash_key (ArgumentError)</span>
</code></pre>
<p>So this has nothing to do with the <code>to_hash</code> conversion.</p>
<p>One way to workaround this is:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">def</span> <span class="nf">my_func</span><span class="p">(</span><span class="o">*</span><span class="n">objects</span><span class="p">,</span> <span class="ss">error_code: </span><span class="mi">400</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="n">kwargs</span>
<span class="k">end</span>
<span class="nb">p</span> <span class="n">my_func</span><span class="p">(</span><span class="ss">to_hash_key: </span><span class="s2">"to_hash"</span><span class="p">)</span> <span class="c1"># => {:to_hash_key=>"to_hash"}</span>
</code></pre>
<p>So adding a keyrest argument (**kwargs), because there is already a keyword argument which means keywords have to fit in the declared keyword args, unless there is a keyrest arg.</p> Ruby master - Bug #14909: Method call with object that has to_hash method crashes (method with splat and keyword arguments)https://redmine.ruby-lang.org/issues/14909?journal_id=729412018-07-13T04:52:21Zfunny_falcon (Yura Sokolov)funny.falcon@gmail.com
<ul></ul><p>Why your object has <code>to_hash</code> method?</p>
<p>Ruby uses long named methods for implicit conversion: <code>to_str</code> - if your object should act as a string, <code>to_int</code> - if your object should act as an integer, <code>to_ary</code> - if your object should act as an array. Looks like same for <code>to_hash</code>.</p>
<p>For explicit conversion short names are used: <code>to_s</code>, <code>to_i</code>, <code>to_a</code>, <code>to_h</code>.</p> Ruby master - Bug #14909: Method call with object that has to_hash method crashes (method with splat and keyword arguments)https://redmine.ruby-lang.org/issues/14909?journal_id=729472018-07-13T15:18:21Zjohannes_luedke (Johannes Lüdke)
<ul></ul><p><a href="https://bugs.ruby-lang.org/issues/14909#note-2" class="external">https://bugs.ruby-lang.org/issues/14909#note-2</a> doesn't resolve the issue for me</p>
<blockquote>
<p>Why your object has to_hash method?</p>
</blockquote>
<p>the objects in question are instances of <code>Dry::Validation::Result</code> (dry-validation gem)</p>
<blockquote>
<p>So, I'd like to propose keeping the current behavior as is, because changing the semantics will bring extra complexity. Instead, just wait for Ruby 3.</p>
</blockquote>
<p>Could this maybe be highlighted in the docs -- to be careful when passing objects that respond <code>to_hash</code> when there are keyword arguments?</p>
<p>In order to make it work both ways, <code>my_func(obj1, obj2, error_code: 422)</code> as well as <code>my_func(obj1, obj2)</code> with a default value for <code>error_code</code>, I ended up doing this workaround:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">def</span> <span class="nf">my_func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span>
<span class="n">opts</span><span class="p">,</span> <span class="n">objects</span> <span class="o">=</span> <span class="n">args</span><span class="p">.</span><span class="nf">partition</span> <span class="p">{</span> <span class="o">|</span><span class="n">el</span><span class="o">|</span> <span class="n">el</span><span class="p">.</span><span class="nf">is_a?</span> <span class="no">Hash</span> <span class="p">}</span>
<span class="n">error_code</span> <span class="o">=</span> <span class="n">opts</span><span class="o">&</span><span class="p">.</span><span class="nf">first</span><span class="o">&</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="ss">:error_code</span><span class="p">,</span> <span class="kp">nil</span><span class="p">)</span> <span class="o">||</span> <span class="mi">400</span>
</code></pre>
<p>It would be cool if ruby would support that out of the box though.</p> Ruby master - Bug #14909: Method call with object that has to_hash method crashes (method with splat and keyword arguments)https://redmine.ruby-lang.org/issues/14909?journal_id=730622018-07-22T06:35:09Zznz (Kazuhiro NISHIYAMA)
<ul><li><strong>Related to</strong> <i><a class="issue tracker-2 status-5 priority-4 priority-default closed" href="/issues/14930">Feature #14930</a>: sample/trick2018</i> added</li></ul> Ruby master - Bug #14909: Method call with object that has to_hash method crashes (method with splat and keyword arguments)https://redmine.ruby-lang.org/issues/14909?journal_id=813322019-09-02T04:48:02Zjeremyevans0 (Jeremy Evans)merch-redmine@jeremyevans.net
<ul><li><strong>Status</strong> changed from <i>Feedback</i> to <i>Closed</i></li></ul><p>The changes in <a class="issue tracker-2 status-5 priority-4 priority-default closed" title="Feature: "Real" keyword argument (Closed)" href="https://redmine.ruby-lang.org/issues/14183">#14183</a> solve this issue. You will now get warnings:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">my_func</span><span class="p">(</span><span class="no">Test</span><span class="p">.</span><span class="nf">new</span><span class="p">)</span>
<span class="c1"># (irb):101: warning: The last argument is used as the keyword parameter</span>
<span class="c1"># (irb):92: warning: for `my_func' defined here</span>
<span class="c1"># ArgumentError (unknown keyword: :to_hash_key)</span>
</code></pre>
<p>In Ruby 3, this will be passed as a positional argument. To get the Ruby 3 behavior with the master branch:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">my_func</span><span class="p">(</span><span class="no">Test</span><span class="p">.</span><span class="nf">new</span><span class="p">,</span> <span class="o">**</span><span class="p">(;{}))</span>
</code></pre> Ruby master - Bug #14909: Method call with object that has to_hash method crashes (method with splat and keyword arguments)https://redmine.ruby-lang.org/issues/14909?journal_id=841782020-02-06T13:25:52ZAlexWayfer (Alexander Popov)alex.wayfer@gmail.com
<ul></ul><p>There are problems with Sequel Models, which have <code>#to_hash</code> method (implicit conversion), and Memery gem, which defines methods with <code>*args, **kwargs, &block</code> (<code>**kwargs</code> was added after warning from Ruby 2.7).</p>
<p>So, Sequel Models as arguments for memoized methods becomes Hashes and it breaks everything.</p>
<p>The <code>**(;{})</code> approach is:</p>
<ul>
<li>unclear for me (what it does in Ruby?);</li>
<li>additional changes at every call;</li>
<li>offenses RuboCop (should be resolved there, yes).</li>
</ul>
<p>I'll try to make dynamic methods definition in Memery with exactly expected parameters instead of <code>*args, **kwargs, &block</code>, but it's harder.</p>
<p>And, probably, impossible at all, like <code>define_method(:bar) do |*instance_method(:foo).parameters|</code>. Only <code>eval</code>, I guess.</p> Ruby master - Bug #14909: Method call with object that has to_hash method crashes (method with splat and keyword arguments)https://redmine.ruby-lang.org/issues/14909?journal_id=841792020-02-06T14:22:41Zjeremyevans0 (Jeremy Evans)merch-redmine@jeremyevans.net
<ul></ul><p>AlexWayfer (Alexander Popov) wrote in <a href="#note-7">#note-7</a>:</p>
<blockquote>
<p>There are problems with Sequel Models, which have <code>#to_hash</code> method (implicit conversion), and Memery gem, which defines methods with <code>*args, **kwargs, &block</code> (<code>**kwargs</code> was added after warning from Ruby 2.7).</p>
</blockquote>
<p>Memery may want to switch to using <code>ruby2_keywords</code> instead.</p>
<blockquote>
<p>So, Sequel Models as arguments for memoized methods becomes Hashes and it breaks everything.</p>
<p>The <code>**(;{})</code> approach is:</p>
<ul>
<li>unclear for me (what it does in Ruby?);</li>
</ul>
</blockquote>
<p>It passes empty keywords, so that the final positional object is not treated as a hash.</p>
<blockquote>
<ul>
<li>additional changes at every call;</li>
</ul>
</blockquote>
<p>Should not be necessary with the <code>ruby2_keywords</code> approach.</p>
<blockquote>
<ul>
<li>offenses RuboCop (should be resolved there, yes).</li>
</ul>
<p>I'll try to make dynamic methods definition in Memery with exactly expected parameters instead of <code>*args, **kwargs, &block</code>, but it's harder.</p>
<p>And, probably, impossible at all, like <code>define_method(:bar) do |*instance_method(:foo).parameters|</code>. Only <code>eval</code>, I guess.</p>
</blockquote>
<p><code>*args, &block</code> as arguments and <code>ruby2_keywords :bar</code> after defining the method should work, and that is the approach I would recommend.</p>