Project

General

Profile

Actions

Feature #16891

closed

Restore Positional Argument to Keyword Conversion

Added by jeremyevans0 (Jeremy Evans) almost 4 years ago. Updated almost 4 years ago.

Status:
Rejected
Target version:
-
[ruby-core:98363]

Description

Based on feedback from Rails-core, Matz has decided to postpone full separation of keyword and positional arguments (see https://discuss.rubyonrails.org/t/new-2-7-3-0-keyword-argument-pain-point/74980). I think most Ruby-core developers were aware that fixing keyword argument separation issues can be fairly painful for libraries and applications that use keyword arguments extensively.

If we are going to discuss reversion or partial reversion of keyword argument separation, I think it would be best to address each aspect of keyword argument separation separately and decide how we want to handle each. There are 4 aspects I am aware of.

Positional Hash Argument to Keyword Argument Conversion

This is by far the most common issue when dealing with keyword argument separation. I would estimate 95% of keyword argument separation issues/warnings are related to this change, and if we are to restore any behavior it should be this behavior. Restoring positional hash argument to keyword argument conversion would allow the following code to continue to work:

def foo(key: 42); end
foo({key: 42})

def bar(*a)
  foo(*a)
end
bar({key: 42})

Attached is a patch that restores the behavior (see below).

Keyword Argument to Mandatory Positional Argument Conversion

This is probably the next most common issue with dealing with keyword argument separation. I'm on the fence about whether we should try to restore this behavior. Restoring keyword argument to mandatory positional argument conversion would allow the following code to continue to work:

def foo(h, **kw); end
foo(key: 42)

I don't think it would be difficult to restore this behavior, but I'm not sure it is worth doing so.

Splitting of Keyword or Positional Hash Argument During Conversion

In Ruby 2.0-2.6, Ruby would split a hash with both symbol keys and non-symbol keys into two hashes, one symbol keyed hash used for keyword arguments, and one non-symbol keyed hash to be passed as a positional argument.

The need for this splitting appears to be rare. I have heard the splitting was not matz's intended behavior originally. I know of only one user relying on this. @mame's original keyword argument separation commit removed the splitting, but I added it back with deprecation warnings to ease the transition. There's actually two types of splitting, one type when splitting keyword argument to positional argument, and another type when splitting positional argument to keyword argument. Restoring the splitting would allow this code to continue to work:

def foo(h={}, key: 42); [h, key] end
foo("key" => 43, key: 42)
foo({"key" => 43, key: 42})

Due to the very low usage of this behavior and questionable semantics, I recommend we do not restore this behavior. Especially because the splitting no longer happens in 2.7 if arbitrary keywords are accepted by the method, since arbitrary keywords support non-symbol keys in 2.7. However, I don't think it is that difficult to restore this support if we decide to do so.

Empty Keyword Argument Splat to Mandatory Positional Argument Conversion

In Ruby 2.7, empty keyword splats are removed and do not pass arguments. However, if the method requires a positional argument, for compatibility Ruby 2.7 passes a new empty positional hash argument. Restoring this behavior would allow the following code to continue to work:

h = {}
def foo(a) a end
foo(**h)

I think very little code relies on this feature, and it would very painful and invasive to restore it, so I recommend we do not restore this behavior.

Proposed behavior for Ruby 3.0

My preference would be to not change behavior at all. However, assuming we want to restore some backwards compatibility in regards to keyword argument separation, my proposal for Ruby 3.0 is to only restore positional argument to keyword argument conversion. We convert positional hash argument to keyword arguments if:

  • Method accepts keyword arguments
  • Method is not passed keyword arguments
  • Method is passed a hash as the final positional argument
  • Method is passed more arguments than the minimum required

If the method does not accept arbitrary keywords, we only convert positional argument to keyword argument if the above are true and the final positional hash is empty or contains a symbol key. It doesn't make sense to convert a positional hash to keywords if it has no symbol keys and the method does not accept arbitrary keywords.

When converting positional arguments to keyword arguments, we issue a warning in verbose mode in 3.0. In 3.1 or 3.2, we can switch the warning to non-verbose mode, before removing it and enforcing full separation in the following version.

I propose to keep the current master branch behavior other than this change.

Proposed behavior for Ruby 2.7.2

I propose we change the positional argument to keyword argument conversion warning to only issue in verbose mode and keep all other behavior the same.

Patch

Attached is a patch that implements my proposal. It does not include test or spec changes, as those will be extensive even for just the change I am proposing. I think we should agree on a proposal before working on changes to tests or specs.


Files

keyword-hash-integration.diff (5.2 KB) keyword-hash-integration.diff jeremyevans0 (Jeremy Evans), 05/14/2020 08:11 PM

Related issues 1 (0 open1 closed)

Related to Ruby master - Feature #16378: Support leading arguments together with ...Closedmatz (Yukihiro Matsumoto)Actions
Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0