Project

General

Profile

Actions

Feature #18648

open

ruby2_keywords and ... name arguments with impossible names

Added by aaronjensen (Aaron Jensen) 3 months ago. Updated 3 months ago.

Status:
Feedback
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:107977]

Description

While investigating a break in a library using reflection, I realized that when ... is used or ruby2_keywords is used that Ruby will name arguments with their symbol, rather than leaving them unnamed. This test demonstrates the issue:

https://github.com/ruby/ruby/blob/97426e15d721119738a548ecfa7232b1d027cd34/test/ruby/test_method.rb#L35
https://github.com/ruby/ruby/blob/97426e15d721119738a548ecfa7232b1d027cd34/test/ruby/test_method.rb#L586

I do not understand how :*, :**, and :& are meant to be considered valid parameter names. I assume the reason is so that they do not conflict with something a person could write but that they can still be referenced in Ruby to facilitate delegation but I just wanted to report that it caused a problem downstream.

It's also curious that:

def foo(*, **, &)
end

Gives these parameter: [[:rest], [:keyrest], [:block, :&]]

Why does only block get the faux name? Is it because that's how yield works so there needs to be a way to reference it in Ruby?

Updated by jeremyevans0 (Jeremy Evans) 3 months ago

  • Backport deleted (2.6: UNKNOWN, 2.7: UNKNOWN, 3.0: UNKNOWN, 3.1: UNKNOWN)
  • Status changed from Open to Feedback
  • Tracker changed from Bug to Feature

This isn't a bug, it's currently expected behavior, even if it is a bit inconsistent. I'll switch this to feature, but if you would like the behavior changed, you'll need to respond with what you would like the behavior changed to. You don't mention what problem was caused downstream, so it's hard to guess what you want. IMO, it seems unlikely the benefits of changing things are worth the backwards compatibility cost.

Updated by bkuhlmann (Brooke Kuhlmann) 3 months ago

Hey Aaron, in regards to *, **, and &, those are passthrough parameters (or bare parameters as I like to call them). For comparison:

# Bare Parameters
def demo(*, **, &) = super

method(:demo).parameters  # => [[:rest], [:keyrest], [:block, :&]]

# Named Parameters
def demo(*one, **two, &three) = super

method(:demo).parameters  # => [[:rest, :one], [:keyrest, :two], [:block, :three]]

Why does only block get the faux name?

One of the uses cases is so that you can pass the anonymous block up to the parent (if you were using inheritance):

Parent = Class.new { def call(&block) = block.call }
Child = Class.new(Parent) { def call(&) = super(&) } 

Child.new.call { "Hi" }  # => "Hi"

This is syntactic sugar for not having to name your block if you don't need to. Support for this was added in Ruby 3.1.0 as is known as an anonymous block argument.

💡 In case it helps, you can also use my Marameters gem to debug all of this further. This gem provides a nice API for accessing all of a method's parameters with minimal effort.

Updated by aaronjensen (Aaron Jensen) 3 months ago

jeremyevans0 (Jeremy Evans) wrote in #note-1:

This isn't a bug, it's currently expected behavior, even if it is a bit inconsistent. I'll switch this to feature, but if you would like the behavior changed, you'll need to respond with what you would like the behavior changed to. You don't mention what problem was caused downstream, so it's hard to guess what you want. IMO, it seems unlikely the benefits of changing things are worth the backwards compatibility cost.

The downstream library effectively copies method signatures. In this instance, it attempted to generate a method signature using the specified name of :** which resulted in a method like def foo(****). We've special cased it to ignore names of *, **, and &. That seems like a work-around though to the

I don't think I'm in a position to make a suggestion as I don't know all of the ramifications. I can just make an observation that generating parameters that have names that are otherwise impossible (afaict) is surprising. I would have expected that the parameters were unnamed, but even that is inconsistent with the block argument. It may just be what is necessary and * and ** are convenient names because they are otherwise impossible.

Updated by aaronjensen (Aaron Jensen) 3 months ago

bkuhlmann (Brooke Kuhlmann) wrote in #note-2:

Hey Aaron, in regards to *, **, and &, those are passthrough parameters (or bare parameters as I like to call them).

Ok, but why doesn't ... create them in this way? Why assign them a name?

Why does only block get the faux name?

One of the uses cases is so that you can pass the anonymous block up to the parent ...
This is syntactic sugar for not having to name your block if you don't need to.

No offense to any who worked on it, but this seems like an unnecessary complexity. "As bloc argument is frequently called just block, the absence of the name doesn’t affect readability." is likely false. It makes anyone who has ever done Ruby have to pause and say what is this? How does it work?

It also introduces a non-backwards compatible change in exchange for that hit to readability.

Actions

Also available in: Atom PDF