[ruby-core:118634] [Ruby master Bug#20640] Evaluation Order Issue in f(**h, &h.delete(key))

Issue #20640 has been reported by jeremyevans0 (Jeremy Evans). ---------------------------------------- Bug #20640: Evaluation Order Issue in f(**h, &h.delete(key)) https://bugs.ruby-lang.org/issues/20640 * Author: jeremyevans0 (Jeremy Evans) * Status: Open * Backport: 3.1: UNKNOWN, 3.2: UNKNOWN, 3.3: UNKNOWN ---------------------------------------- Since Ruby 3.0, there is an evaluation order issue when passing a single keyword splat and a block pass expression that modifies the keyword splat: ```ruby def f(*a, **kw) kw[:a].class end h = {a: ->{}} f(**h, &h.delete(:a)) # Ruby 2.0 - 2.7: Proc # Ruby 3.0 - 3.4: NilClass ``` For single keyword splats followed by positional argument splats, this has been an issue since 3.3: ```ruby def f(*a, **kw) kw[:a].class end h = {a: ->{}} a = [] f(*a, **h, &h.delete(:a)) # Ruby 2.0 - 3.2: Proc # Ruby 3.3 - 3.4: NilClass ``` Ruby handles these issues for positional splats, duplicating the splatted array before evaluating post, keyword, or block argument expressions: ```ruby f(*a, a.pop) # post argument f(*a, **a.pop) # keyword splat argument f(*a, a: a.pop) # keyword argument f(*a, &a.pop) # block argument ``` So it should handle the case for a keyword splat that could potentially be modified by block argument expression. I'll submit a pull request shortly to fix this issue. -- https://bugs.ruby-lang.org/

Issue #20640 has been updated by jeremyevans0 (Jeremy Evans). Pull request: https://github.com/ruby/ruby/pull/11206 ---------------------------------------- Bug #20640: Evaluation Order Issue in f(**h, &h.delete(key)) https://bugs.ruby-lang.org/issues/20640#change-109162 * Author: jeremyevans0 (Jeremy Evans) * Status: Open * Backport: 3.1: UNKNOWN, 3.2: UNKNOWN, 3.3: UNKNOWN ---------------------------------------- Since Ruby 3.0, there is an evaluation order issue when passing a single keyword splat and a block pass expression that modifies the keyword splat: ```ruby def f(*a, **kw) kw[:a].class end h = {a: ->{}} f(**h, &h.delete(:a)) # Ruby 2.0 - 2.7: Proc # Ruby 3.0 - 3.4: NilClass ``` For single keyword splats followed by positional argument splats, this has been an issue since 3.3: ```ruby def f(*a, **kw) kw[:a].class end h = {a: ->{}} a = [] f(*a, **h, &h.delete(:a)) # Ruby 2.0 - 3.2: Proc # Ruby 3.3 - 3.4: NilClass ``` Ruby handles these issues for positional splats, duplicating the splatted array before evaluating post, keyword, or block argument expressions: ```ruby f(*a, a.pop) # post argument f(*a, **a.pop) # keyword splat argument f(*a, a: a.pop) # keyword argument f(*a, &a.pop) # block argument ``` So it should handle the case for a keyword splat that could potentially be modified by block argument expression. I'll submit a pull request shortly to fix this issue. -- https://bugs.ruby-lang.org/

Issue #20640 has been updated by matz (Yukihiro Matsumoto). Assignee set to jeremyevans0 (Jeremy Evans) The behavior of modifying the splatting object in `to_proc` has been flaky in the history of the language. I think the current behavior is a bit inconsistent and should be fixed (only if there's no performance penalty). Matz. ---------------------------------------- Bug #20640: Evaluation Order Issue in f(**h, &h.delete(key)) https://bugs.ruby-lang.org/issues/20640#change-109321 * Author: jeremyevans0 (Jeremy Evans) * Status: Open * Assignee: jeremyevans0 (Jeremy Evans) * Backport: 3.1: UNKNOWN, 3.2: UNKNOWN, 3.3: UNKNOWN ---------------------------------------- Since Ruby 3.0, there is an evaluation order issue when passing a single keyword splat and a block pass expression that modifies the keyword splat: ```ruby def f(*a, **kw) kw[:a].class end h = {a: ->{}} f(**h, &h.delete(:a)) # Ruby 2.0 - 2.7: Proc # Ruby 3.0 - 3.4: NilClass ``` For single keyword splats followed by positional argument splats, this has been an issue since 3.3: ```ruby def f(*a, **kw) kw[:a].class end h = {a: ->{}} a = [] f(*a, **h, &h.delete(:a)) # Ruby 2.0 - 3.2: Proc # Ruby 3.3 - 3.4: NilClass ``` Ruby handles these issues for positional splats, duplicating the splatted array before evaluating post, keyword, or block argument expressions: ```ruby f(*a, a.pop) # post argument f(*a, **a.pop) # keyword splat argument f(*a, a: a.pop) # keyword argument f(*a, &a.pop) # block argument ``` So it should handle the case for a keyword splat that could potentially be modified by block argument expression. I'll submit a pull request shortly to fix this issue. -- https://bugs.ruby-lang.org/

Issue #20640 has been updated by mame (Yusuke Endoh). Briefly discussed at the dev meeting, and we found more pedantic case. We would like to see how much of a performance penalty it would bring to see if it should be fixed. ```ruby def f(*a, **kw) kw[:a].class end h = {a: 1} foo = Object.new foo.define_singleton_method(:to_proc) do h.clear proc {} end p f(**h, &foo) #=> expected: Integer, actual: NilClass ``` ---------------------------------------- Bug #20640: Evaluation Order Issue in f(**h, &h.delete(key)) https://bugs.ruby-lang.org/issues/20640#change-109322 * Author: jeremyevans0 (Jeremy Evans) * Status: Open * Assignee: jeremyevans0 (Jeremy Evans) * Backport: 3.1: UNKNOWN, 3.2: UNKNOWN, 3.3: UNKNOWN ---------------------------------------- Since Ruby 3.0, there is an evaluation order issue when passing a single keyword splat and a block pass expression that modifies the keyword splat: ```ruby def f(*a, **kw) kw[:a].class end h = {a: ->{}} f(**h, &h.delete(:a)) # Ruby 2.0 - 2.7: Proc # Ruby 3.0 - 3.4: NilClass ``` For single keyword splats followed by positional argument splats, this has been an issue since 3.3: ```ruby def f(*a, **kw) kw[:a].class end h = {a: ->{}} a = [] f(*a, **h, &h.delete(:a)) # Ruby 2.0 - 3.2: Proc # Ruby 3.3 - 3.4: NilClass ``` Ruby handles these issues for positional splats, duplicating the splatted array before evaluating post, keyword, or block argument expressions: ```ruby f(*a, a.pop) # post argument f(*a, **a.pop) # keyword splat argument f(*a, a: a.pop) # keyword argument f(*a, &a.pop) # block argument ``` So it should handle the case for a keyword splat that could potentially be modified by block argument expression. I'll submit a pull request shortly to fix this issue. -- https://bugs.ruby-lang.org/

Issue #20640 has been updated by Eregon (Benoit Daloze). IMO this kind of issue is not worth fixing, creating extra copies for this seems clearly not worth it (in perf hit vs very minimal gain). It is a bug of the user code to mutate arguments while passing them, the user code should be fixed to not do that. IOW the examples above make no sense and are unclear at best, relying on any specific semantics there is extremely brittle, and there is no value to do this in the first place anyway. ---------------------------------------- Bug #20640: Evaluation Order Issue in f(**h, &h.delete(key)) https://bugs.ruby-lang.org/issues/20640#change-109323 * Author: jeremyevans0 (Jeremy Evans) * Status: Open * Assignee: jeremyevans0 (Jeremy Evans) * Backport: 3.1: UNKNOWN, 3.2: UNKNOWN, 3.3: UNKNOWN ---------------------------------------- Since Ruby 3.0, there is an evaluation order issue when passing a single keyword splat and a block pass expression that modifies the keyword splat: ```ruby def f(*a, **kw) kw[:a].class end h = {a: ->{}} f(**h, &h.delete(:a)) # Ruby 2.0 - 2.7: Proc # Ruby 3.0 - 3.4: NilClass ``` For single keyword splats followed by positional argument splats, this has been an issue since 3.3: ```ruby def f(*a, **kw) kw[:a].class end h = {a: ->{}} a = [] f(*a, **h, &h.delete(:a)) # Ruby 2.0 - 3.2: Proc # Ruby 3.3 - 3.4: NilClass ``` Ruby handles these issues for positional splats, duplicating the splatted array before evaluating post, keyword, or block argument expressions: ```ruby f(*a, a.pop) # post argument f(*a, **a.pop) # keyword splat argument f(*a, a: a.pop) # keyword argument f(*a, &a.pop) # block argument ``` So it should handle the case for a keyword splat that could potentially be modified by block argument expression. I'll submit a pull request shortly to fix this issue. -- https://bugs.ruby-lang.org/

Issue #20640 has been updated by jeremyevans0 (Jeremy Evans). mame (Yusuke Endoh) wrote in #note-3:
Briefly discussed at the dev meeting, and we found more pedantic case. We would like to see how much of a performance penalty it would bring to see if it should be fixed.
```ruby def f(*a, **kw) kw[:a].class end
h = {a: 1} foo = Object.new foo.define_singleton_method(:to_proc) do h.clear proc {} end p f(**h, &foo) #=> expected: Integer, actual: NilClass ```
There would likely be a significant performance penalty. Ever method call with splat and keyword splat, splat and block, or keyword splat and block, would need at least VM instructions added to check the types of the arguments, or all such calls would need to allocate when they currently do not. My understanding was that we accept that implicit conversion methods that modify other objects can cause problems, we only try to protect against expressions in the method call causing problems. ---------------------------------------- Bug #20640: Evaluation Order Issue in f(**h, &h.delete(key)) https://bugs.ruby-lang.org/issues/20640#change-109328 * Author: jeremyevans0 (Jeremy Evans) * Status: Open * Assignee: jeremyevans0 (Jeremy Evans) * Backport: 3.1: UNKNOWN, 3.2: UNKNOWN, 3.3: UNKNOWN ---------------------------------------- Since Ruby 3.0, there is an evaluation order issue when passing a single keyword splat and a block pass expression that modifies the keyword splat: ```ruby def f(*a, **kw) kw[:a].class end h = {a: ->{}} f(**h, &h.delete(:a)) # Ruby 2.0 - 2.7: Proc # Ruby 3.0 - 3.4: NilClass ``` For single keyword splats followed by positional argument splats, this has been an issue since 3.3: ```ruby def f(*a, **kw) kw[:a].class end h = {a: ->{}} a = [] f(*a, **h, &h.delete(:a)) # Ruby 2.0 - 3.2: Proc # Ruby 3.3 - 3.4: NilClass ``` Ruby handles these issues for positional splats, duplicating the splatted array before evaluating post, keyword, or block argument expressions: ```ruby f(*a, a.pop) # post argument f(*a, **a.pop) # keyword splat argument f(*a, a: a.pop) # keyword argument f(*a, &a.pop) # block argument ``` So it should handle the case for a keyword splat that could potentially be modified by block argument expression. I'll submit a pull request shortly to fix this issue. -- https://bugs.ruby-lang.org/
participants (4)
-
Eregon (Benoit Daloze)
-
jeremyevans0 (Jeremy Evans)
-
mame (Yusuke Endoh)
-
matz (Yukihiro Matsumoto)