Skip to content

Commit

Permalink
[GR-18163] Fix destructuring of Proc's single Array argument
Browse files Browse the repository at this point in the history
PullRequest: truffleruby/3525
  • Loading branch information
andrykonchin committed Nov 2, 2022
2 parents 5732464 + 0a60af5 commit c00d9f4
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Compatibility:
* Fixed `rb_gv_get` so that it no longer implicitly creates global variables (#2748, @nirvdrum).
* Added implementations of `rb_gvar_val_getter` and `rb_define_virtual_variable` (#2750, @nirvdrum).
* Implement `rb_warning_category_enabled_p` to support the `syntax_tree` gem (#2764, @andrykonchin).
* Fix desctructuring of a single block argument that implements `#to_ary` dynamically (#2719, @andrykonchin).

Performance:

Expand Down
43 changes: 43 additions & 0 deletions spec/ruby/language/block_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -263,12 +263,55 @@ def m(a) yield a end
m(obj) { |a, b, c| [a, b, c] }.should == [obj, nil, nil]
end

it "receives the object if it does not respond to #to_ary" do
obj = Object.new

m(obj) { |a, b, c| [a, b, c] }.should == [obj, nil, nil]
end

it "calls #respond_to? to check if object has method #to_ary" do
obj = mock("destructure block arguments")
obj.should_receive(:respond_to?).with(:to_ary, true).and_return(true)
obj.should_receive(:to_ary).and_return([1, 2])

m(obj) { |a, b, c| [a, b, c] }.should == [1, 2, nil]
end

it "receives the object if it does not respond to #respond_to?" do
obj = BasicObject.new

m(obj) { |a, b, c| [a, b, c] }.should == [obj, nil, nil]
end

it "calls #to_ary on the object when it is defined dynamically" do
obj = Object.new
def obj.method_missing(name, *args, &block)
if name == :to_ary
[1, 2]
else
super
end
end
def obj.respond_to_missing?(name, include_private)
name == :to_ary
end

m(obj) { |a, b, c| [a, b, c] }.should == [1, 2, nil]
end

it "raises a TypeError if #to_ary does not return an Array" do
obj = mock("destructure block arguments")
obj.should_receive(:to_ary).and_return(1)

-> { m(obj) { |a, b| } }.should raise_error(TypeError)
end

it "raises error transparently if #to_ary raises error on its own" do
obj = Object.new
def obj.to_ary; raise "Exception raised in #to_ary" end

-> { m(obj) { |a, b| } }.should raise_error(RuntimeError, "Exception raised in #to_ary")
end
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.profiles.BranchProfile;
import org.truffleruby.language.RubyNode;
import org.truffleruby.language.dispatch.InternalRespondToNode;
import org.truffleruby.language.dispatch.DispatchNode;

public class ShouldDestructureNode extends RubyContextSourceNode {

@Child private InternalRespondToNode respondToToAry;
@Child private DispatchNode respondToToAry;

private final boolean keywordArguments;
private final BranchProfile checkIsArrayProfile = BranchProfile.create();
Expand All @@ -41,20 +41,25 @@ public Object execute(VirtualFrame frame) {

checkIsArrayProfile.enter();

final Object firstArgument = RubyArguments.getArgument(frame, 0);
final Object singleArgument = RubyArguments.getArgument(frame, 0);

if (RubyGuards.isRubyArray(firstArgument)) {
if (RubyGuards.isRubyArray(singleArgument)) {
return true;
}

if (respondToToAry == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
respondToToAry = insert(InternalRespondToNode.create());
respondToToAry = insert(DispatchNode.create(DispatchNode.PRIVATE_RETURN_MISSING));
}

// TODO(cseaton): check this is actually a static "find if there is such method" and not a
// dynamic call to respond_to?
return respondToToAry.execute(frame, firstArgument, "to_ary");
var respondToCallResult = respondToToAry.call(singleArgument, "respond_to?", getLanguage().coreSymbols.TO_ARY);
// the object may not have the #respond_to? method (e.g. an instance of BasicObject class)
if (respondToCallResult == DispatchNode.MISSING) {
return false;
}

assert respondToCallResult instanceof Boolean;
return (boolean) respondToCallResult;
}

@Override
Expand Down

0 comments on commit c00d9f4

Please sign in to comment.