Project

General

Profile

Actions

Feature #19134

closed

** is not allowed in def foo(...)

Added by shugo (Shugo Maeda) over 1 year ago. Updated over 1 year ago.

Status:
Closed
Target version:
[ruby-dev:51203]

Description

* and & are allowed in the body of a method with ... argument forwarding, but ** is not allowed.

def foo(...)
  bar(*) # OK
  baz(&) # OK
  quux(**) # NG
end

Is it intended behavior?

It seems that parse.y has code like #ifdef RUBY3_KEYWORDS, and if RUBY3_KEYWORDS, ** will also be supported.


Related issues 1 (1 open0 closed)

Related to Ruby master - Bug #19165: Method (with no param) delegation with *, **, and ... is slowOpenActions

Updated by shugo (Shugo Maeda) over 1 year ago

I've created a pull request: https://github.com/ruby/ruby/pull/6818

With this change a test of rbs.gem fails because argument types of def foo(...) is changed to (*untyped, **untyped **) from (*untyped), but I believe it's right.
(however, the specifal variable name ** should be hidden by rbs.gem.)
I've created a issue: https://github.com/ruby/rbs/issues/1163

Actions #3

Updated by shugo (Shugo Maeda) over 1 year ago

  • Status changed from Open to Closed

Applied in changeset git|4fc668a4f3b9b67cc7566096ab55cab34c67c158.


Allow ** in def foo(...)

[Feature #19134]

Updated by shugo (Shugo Maeda) over 1 year ago

matz (Yukihiro Matsumoto) wrote in #note-2:

LGTM.

Thank you. I've merged it.

I realized that my fix also chaged the behavior of the following code:

def foo(*, **, &)
  bar(...)
end

def bar(*args, **kw, &block)
  p [args, kw, block&.call]
end

foo(1, 2, x: 3, y: 4) { 5 }

My fix changed the result from [[1, 2], {}, 5] to [[1, 2], {:x=>3, :y=>4}, 5].
So ... is now a syntax sugar of *, **, &.

Updated by Eregon (Benoit Daloze) over 1 year ago

IMHO if ... is used then *, ** and & should all be forbidden (a SyntaxError at parse time).
Because that way is the best for optimizing delegation.
And also taking apart * and ** is arguably not really delegation anymore.

Actions #6

Updated by Eregon (Benoit Daloze) over 1 year ago

  • Related to Bug #19165: Method (with no param) delegation with *, **, and ... is slow added

Updated by Eregon (Benoit Daloze) over 1 year ago

Although, it should still be possible to optimize delegation as good as possible and allow */**/& by having those behave like def args(*,**,&) = *, def kwargs(*,**,&) = **, def block(*,**,&) = & and as if * was replaced by args(...), etc, but that's of course at the expense of making the handling of those slightly slower and more complicated (but at least it doesn't slow down delegation via (...)).

Updated by shugo (Shugo Maeda) over 1 year ago

Eregon (Benoit Daloze) wrote in #note-5:

IMHO if ... is used then *, ** and & should all be forbidden (a SyntaxError at parse time).
Because that way is the best for optimizing delegation.
And also taking apart * and ** is arguably not really delegation anymore.

& is allowed in 3.1, so it's a breaking change to prohibit it.

Updated by shugo (Shugo Maeda) over 1 year ago

  • Status changed from Closed to Open

I think it's most conservative to probhibit * and **:

def foo(...)
  bar(&) # OK
  baz(*) # error
  quux(**) # error
end

And an error should occur in the following code instead of dropping keyword arguments:

def foo(*, **, &)
  bar(...)
end

def bar(*args, **kw, &block)
  p [args, kw, block&.call]
end

foo(1, 2, x: 3, y: 4) { 5 }

What do you think, Matz?

Actions #10

Updated by hsbt (Hiroshi SHIBATA) over 1 year ago

  • Target version set to 3.2

Updated by Eregon (Benoit Daloze) over 1 year ago

shugo (Shugo Maeda) wrote in #note-8:

& is allowed in 3.1, so it's a breaking change to prohibit it.

Right and & is not problematic re performance, it's a completely separate argument anyway.
I agree with your proposal in your comment just above this one.

Updated by mame (Yusuke Endoh) over 1 year ago

IMHO if ... is used then *, ** and & should all be forbidden (a SyntaxError at parse time).

I agree with this. If you want to do that, you should take it with def foo(*, **, &).

I feel that only & is acceptable since a block is implicitly passed. But I don't think * and ** make sense inside def foo(...).

Actions #13

Updated by shugo (Shugo Maeda) over 1 year ago

  • Status changed from Open to Closed

Applied in changeset git|2581de112c1957dc4b5852e54337551dc8972c99.


Disallow mixed usage of ... and /*

[Feature #19134]

Updated by shugo (Shugo Maeda) over 1 year ago

mame (Yusuke Endoh) wrote in #note-12:

I feel that only & is acceptable since a block is implicitly passed. But I don't think * and ** make sense inside def foo(...).

Speaking of implicity of blocks, def f; g(&); end causes a syntax error, so it may be better to allow it, or disallow def f(...); g(&); end in the future versions.
Anyway, I've fixed the behavior of ... as described in #note-9.

Updated by Eregon (Benoit Daloze) over 1 year ago

shugo (Shugo Maeda) wrote in #note-14:

Speaking of implicity of blocks, def f; g(&); end causes a syntax error, so it may be better to allow it, or disallow def f(...); g(&); end in the future versions.

Since some time @ko1 (Koichi Sasada) worked on no implicit block if not a parameter of the method (with the exception of yield in the body). I think that's a good property, so I think it's good for def f; g(&); end to be a syntax error.

For def f(...); g(&); end "..." is "capture all arguments, positional, keyword and block" so I think it's more natural there to be able to access the block (although I can't think of when that would be useful, maybe def f(...); g(&); h(...) end when h is known to ignore the block but that feels hacky, better use (*, **, &) parameters then).

Anyway, I've fixed the behavior of ... as described in #note-9.

Thanks!

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0