Issue #16891 has been updated by sam.saffron (Sam Saffron).


I agree many gems already work, but we have built ourselves a time bomb here.

`ruby2_keywords` is certainly going to go away one day, it is too ugly to keep permanently. What happens when it goes away? We need to then swap our implementation to a backwards compatible way that 2.7 supports. But the only alternative it supports is often a slower alternative. 


> Personally, I don't think we should remove ruby2_keywords until there is an alternative approach that is equally efficient.

> It sounds like such an approach will be slower than the ruby2_keywords approach due to the additional object allocation.

Let me expand with a slightly alternative syntax for clarity: 


```
def foo_old(*args)
  @track = args
  delegate(*args)
end

def foo_new(...args)
  @track = args
  delegate(...)
end

def foo_now(*args, **kwargs)
  @track = [args, kwargs]
  delegate(*args, **kwargs)
end
```

Now lets consider allocations:

`foo`

- old .. 1 Array allocation
- new .. 1 Arguments object allocation
- now .. 1 Array allocation + 1 Hash allocation + 1 Array allocation

`foo(1,2)`


- old .. 1 Array allocation
- new .. 1 Arguments object allocation
- now .. 1 Array allocation + 1 Hash allocation + 1 Array allocation

`foo(1, a: 1)`

- old .. 1 Array allocation
- new .. 1 Arguments object allocation
- now .. 1 Array allocation + 1 Hash allocation + 1 Array allocation


Old syntax and new syntax both allocate exactly the same amount ... 1 wrapping RVALUE. 

New syntax sparks a lot more joy for quite a few reasons:

```
def foo(...a)
  puts a[:bar]
  puts a[0]
  puts a.length
  puts a.to_a
end

foo(1, 2, a: 1, bar: 2)
a[:bar] == 2
a[0] == 1
a.length == 4 # delightful cause *args would be length 3 which is confusing since there really are 4 args. 
a.to_a == [1, 2, {a: 1, bar: 2}] # arguably we could make it `[1, 2, {a: 1} , {bar: 2}]

```

Arguments would be implemented in C which provides some big advantages like deferred materialization of the list of args. It would also implement its on version of `rb_ary_equal` which can be tuned to be very fast.


End result is that `...a` would not only be as fast as `*a` is today but a bit faster in certain use cases. 


----------------------------------------
Feature #16891: Restore Positional Argument to Keyword Conversion
https://bugs.ruby-lang.org/issues/16891#change-85706

* Author: jeremyevans0 (Jeremy Evans)
* Status: Open
* Priority: Normal
* Assignee: matz (Yukihiro Matsumoto)
----------------------------------------
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:

```ruby
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:

```ruby
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:

```ruby
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:

```ruby
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)


-- 
https://bugs.ruby-lang.org/

Unsubscribe: <mailto:ruby-core-request / ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>