From c6a3f5c7902bac407b0e8ae64fd4bf7c2d4984ee Mon Sep 17 00:00:00 2001 From: Nicholas Koh Date: Thu, 1 Feb 2024 17:09:51 +0800 Subject: [PATCH 1/2] Allow Array#inject to accept string in addition to symbol --- .../truffleruby/core/array/ArrayNodes.java | 124 +++++++++++++++--- 1 file changed, 104 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/truffleruby/core/array/ArrayNodes.java b/src/main/java/org/truffleruby/core/array/ArrayNodes.java index 0acb446d710..75aad99effd 100644 --- a/src/main/java/org/truffleruby/core/array/ArrayNodes.java +++ b/src/main/java/org/truffleruby/core/array/ArrayNodes.java @@ -1305,27 +1305,30 @@ private static final class State { // Uses block and no Symbol - @Specialization(guards = { "isEmptyArray(array)", "wasProvided(initialOrSymbol)" }) - Object injectEmptyArray(RubyArray array, Object initialOrSymbol, NotProvided symbol, RubyProc block) { - return initialOrSymbol; + @Specialization(guards = { "isEmptyArray(array)", "wasProvided(initialOrSymbolOrString)" }) + Object injectEmptyArray( + RubyArray array, Object initialOrSymbolOrString, NotProvided symbolOrString, RubyProc block) { + return initialOrSymbolOrString; } @Specialization(guards = { "isEmptyArray(array)" }) Object injectEmptyArrayNoInitial( - RubyArray array, NotProvided initialOrSymbol, NotProvided symbol, RubyProc block) { + RubyArray array, NotProvided initialOrSymbolOrString, NotProvided symbolOrString, RubyProc block) { return nil; } - @Specialization(guards = { "!isEmptyArray(array)", "wasProvided(initialOrSymbol)" }) - Object injectWithInitial(RubyArray array, Object initialOrSymbol, NotProvided symbol, RubyProc block, + @Specialization(guards = { "!isEmptyArray(array)", "wasProvided(initialOrSymbolOrString)" }) + Object injectWithInitial( + RubyArray array, Object initialOrSymbolOrString, NotProvided symbolOrString, RubyProc block, @Cached @Shared ArrayEachIteratorNode iteratorNode) { - return injectBlockHelper(array, block, initialOrSymbol, 0, iteratorNode); + return injectBlockHelper(array, block, initialOrSymbolOrString, 0, iteratorNode); } @Specialization( guards = { "!isEmptyArray(array)" }, limit = "storageStrategyLimit()") - Object injectNoInitial(RubyArray array, NotProvided initialOrSymbol, NotProvided symbol, RubyProc block, + Object injectNoInitial( + RubyArray array, NotProvided initialOrSymbolOrString, NotProvided symbolOrString, RubyProc block, @Bind("array.getStore()") Object store, @CachedLibrary("store") ArrayStoreLibrary stores, @Cached @Shared ArrayEachIteratorNode iteratorNode) { @@ -1353,23 +1356,28 @@ public void accept(Node node, CallBlockNode yieldNode, RubyArray array, Object s @Specialization(guards = { "isEmptyArray(array)" }) Object injectSymbolEmptyArrayNoInitial( - RubyArray array, RubySymbol initialOrSymbol, NotProvided symbol, Nil block) { + RubyArray array, RubySymbol initialOrSymbolOrString, NotProvided symbolOrString, Nil block) { return nil; } @Specialization( guards = { "isEmptyArray(array)", - "wasProvided(initialOrSymbol)" }) - Object injectSymbolEmptyArray(RubyArray array, Object initialOrSymbol, RubySymbol symbol, Nil block) { - return initialOrSymbol; + "wasProvided(initialOrSymbolOrString)" }) + Object injectSymbolEmptyArray( + RubyArray array, Object initialOrSymbolOrString, RubySymbol symbolOrString, Nil block) { + return initialOrSymbolOrString; } @Specialization( guards = { "!isEmptyArray(array)" }, limit = "storageStrategyLimit()") Object injectSymbolNoInitial( - VirtualFrame frame, RubyArray array, RubySymbol initialOrSymbol, NotProvided symbol, Nil block, + VirtualFrame frame, + RubyArray array, + RubySymbol initialOrSymbolOrString, + NotProvided symbolOrString, + Nil block, @Bind("array.getStore()") Object store, @CachedLibrary("store") ArrayStoreLibrary stores, @Cached @Shared IntValueProfile arraySizeProfile, @@ -1378,7 +1386,7 @@ Object injectSymbolNoInitial( return injectSymbolHelper( frame, array, - toJavaString.execute(this, initialOrSymbol), + toJavaString.execute(this, initialOrSymbolOrString), stores, store, stores.read(store, 0), @@ -1390,10 +1398,14 @@ Object injectSymbolNoInitial( @Specialization( guards = { "!isEmptyArray(array)", - "wasProvided(initialOrSymbol)" }, + "wasProvided(initialOrSymbolOrString)" }, limit = "storageStrategyLimit()") Object injectSymbolWithInitial( - VirtualFrame frame, RubyArray array, Object initialOrSymbol, RubySymbol symbol, Nil block, + VirtualFrame frame, + RubyArray array, + Object initialOrSymbolOrString, + RubySymbol symbolOrString, + Nil block, @Bind("array.getStore()") Object store, @CachedLibrary("store") ArrayStoreLibrary stores, @Cached @Shared IntValueProfile arraySizeProfile, @@ -1402,23 +1414,95 @@ Object injectSymbolWithInitial( return injectSymbolHelper( frame, array, - toJavaString.execute(this, symbol), + toJavaString.execute(this, symbolOrString), stores, store, - initialOrSymbol, + initialOrSymbolOrString, 0, arraySizeProfile, loopProfile); } - private Object injectSymbolHelper(VirtualFrame frame, RubyArray array, String symbol, + // Uses String and no block + + @Specialization(guards = { "isEmptyArray(array)" }) + Object injectStringEmptyArrayNoInitial( + RubyArray array, RubyString initialOrSymbolOrString, NotProvided symbolOrString, Nil block) { + return nil; + } + + @Specialization( + guards = { + "isEmptyArray(array)", + "wasProvided(initialOrSymbolOrString)" }) + Object injectStringEmptyArray( + RubyArray array, Object initialOrSymbolOrString, RubyString symbolOrString, Nil block) { + return initialOrSymbolOrString; + } + + @Specialization( + guards = { "!isEmptyArray(array)" }, + limit = "storageStrategyLimit()") + Object injectStringNoInitial( + VirtualFrame frame, + RubyArray array, + RubyString initialOrSymbolOrString, + NotProvided symbolOrString, + Nil block, + @Bind("array.getStore()") Object store, + @CachedLibrary("store") ArrayStoreLibrary stores, + @Cached @Shared IntValueProfile arraySizeProfile, + @Cached @Exclusive LoopConditionProfile loopProfile, + @Cached @Shared ToJavaStringNode toJavaString) { + return injectSymbolHelper( + frame, + array, + toJavaString.execute(this, initialOrSymbolOrString), + stores, + store, + stores.read(store, 0), + 1, + arraySizeProfile, + loopProfile); + } + + @Specialization( + guards = { + "!isEmptyArray(array)", + "wasProvided(initialOrSymbolOrString)" }, + limit = "storageStrategyLimit()") + Object injectSymbolWithInitial( + VirtualFrame frame, + RubyArray array, + Object initialOrSymbolOrString, + RubyString symbolOrString, + Nil block, + @Bind("array.getStore()") Object store, + @CachedLibrary("store") ArrayStoreLibrary stores, + @Cached @Shared IntValueProfile arraySizeProfile, + @Cached @Exclusive LoopConditionProfile loopProfile, + @Cached @Shared ToJavaStringNode toJavaString) { + return injectSymbolHelper( + frame, + array, + toJavaString.execute(this, symbolOrString), + stores, + store, + initialOrSymbolOrString, + 0, + arraySizeProfile, + loopProfile); + } + + private Object injectSymbolHelper(VirtualFrame frame, RubyArray array, String symbolOrString, ArrayStoreLibrary stores, Object store, Object initial, int start, IntValueProfile arraySizeProfile, LoopConditionProfile loopProfile) { Object accumulator = initial; int n = start; try { for (; loopProfile.inject(n < arraySizeProfile.profile(array.size)); n++) { - accumulator = dispatch.callWithFrame(PUBLIC, frame, accumulator, symbol, stores.read(store, n)); + accumulator = dispatch.callWithFrame(PUBLIC, frame, accumulator, symbolOrString, + stores.read(store, n)); TruffleSafepoint.poll(this); } } finally { From 707c9de7b05679f124a001051750f992b9a9bca5 Mon Sep 17 00:00:00 2001 From: Nicholas Koh Date: Thu, 1 Feb 2024 17:12:04 +0800 Subject: [PATCH 2/2] Add specs for inject --- spec/ruby/core/enumerable/shared/inject.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/ruby/core/enumerable/shared/inject.rb b/spec/ruby/core/enumerable/shared/inject.rb index 693d34d675d..a24e2c59abd 100644 --- a/spec/ruby/core/enumerable/shared/inject.rb +++ b/spec/ruby/core/enumerable/shared/inject.rb @@ -18,6 +18,10 @@ EnumerableSpecs::Numerous.new(1, 2, 3).send(@method, 10, :-).should == 4 end + it "can take a string as the second argument" do + EnumerableSpecs::Numerous.new(1, 2, 3).send(@method, 10, "-").should == 4 + end + it "ignores the block if two arguments" do -> { EnumerableSpecs::Numerous.new(1, 2, 3).send(@method, 10, :-) { raise "we never get here"}.should == 4 @@ -41,6 +45,10 @@ EnumerableSpecs::Numerous.new(10, 1, 2, 3).send(@method, :-).should == 4 end + it "can take a string argument" do + EnumerableSpecs::Numerous.new(10, 1, 2, 3).send(@method, "-").should == 4 + end + it "without argument takes a block with an accumulator (with first element as initial value) and the current element. Value of block becomes new accumulator" do a = [] EnumerableSpecs::Numerous.new.send(@method) { |memo, i| a << [memo, i]; i }