Skip to content

Commit 14e0a99

Browse files
author
Nicolas Laurent
committed
[GR-20070] Make String#concat works with multiple or no arguments.
PullRequest: truffleruby/1181
2 parents 1adb157 + 1bd299f commit 14e0a99

File tree

3 files changed

+56
-13
lines changed

3 files changed

+56
-13
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Bug fixes:
3838
* `Truffle::Interop.keys` should report methods of String and Symbol (#1817)
3939
* `Kernel#sprintf` encoding validity has been fixed (#1852, @XrXr).
4040
* Fixed File.fnmatch causes ArrayIndexOutOfBoundsException (#1845).
41+
* Make `String#concat` work with no or multiple arguments (#1519).
4142

4243
Compatibility:
4344

spec/tags/core/string/concat_tags.txt

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/main/java/org/truffleruby/core/string/StringNodes.java

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
import static org.truffleruby.core.rope.CodeRange.CR_BROKEN;
6767
import static org.truffleruby.core.rope.CodeRange.CR_UNKNOWN;
6868
import static org.truffleruby.core.rope.RopeConstants.EMPTY_ASCII_8BIT_ROPE;
69+
import static org.truffleruby.core.string.StringOperations.createString;
6970
import static org.truffleruby.core.string.StringOperations.encoding;
7071
import static org.truffleruby.core.string.StringOperations.rope;
7172
import static org.truffleruby.core.string.StringSupport.MBCLEN_CHARFOUND_LEN;
@@ -449,25 +450,69 @@ protected int compare(DynamicObject a, DynamicObject b,
449450

450451
}
451452

452-
@CoreMethod(names = { "<<", "concat" }, required = 1, taintFrom = 1, raiseIfFrozenSelf = true)
453+
@CoreMethod(names = { "<<", "concat" }, optional = 1, rest = true, taintFrom = 1, raiseIfFrozenSelf = true)
453454
@ImportStatic(StringGuards.class)
454455
public abstract static class ConcatNode extends CoreMethodArrayArgumentsNode {
455456

456-
@Specialization(guards = "isRubyString(other)")
457-
protected DynamicObject concat(DynamicObject string, DynamicObject other,
457+
public static ConcatNode create() {
458+
return StringNodesFactory.ConcatNodeFactory.create(null);
459+
}
460+
461+
public abstract Object executeConcat(DynamicObject string, Object first, Object[] rest);
462+
463+
@Specialization(guards = "rest.length == 0")
464+
protected DynamicObject concatZero(DynamicObject string, NotProvided first, Object[] rest) {
465+
return string;
466+
}
467+
468+
@Specialization(guards = { "rest.length == 0", "isRubyString(first)" })
469+
protected DynamicObject concat(DynamicObject string, DynamicObject first, Object[] rest,
458470
@Cached StringAppendPrimitiveNode stringAppendNode) {
459-
return stringAppendNode.executeStringAppend(string, other);
471+
return stringAppendNode.executeStringAppend(string, first);
460472
}
461473

462-
@Specialization(guards = "!isRubyString(other)")
463-
protected Object concatGeneric(
464-
VirtualFrame frame,
465-
DynamicObject string,
466-
Object other,
474+
@Specialization(guards = { "rest.length == 0", "wasProvided(first)", "!isRubyString(first)" })
475+
protected Object concatGeneric(DynamicObject string, Object first, Object[] rest,
467476
@Cached("createPrivate()") CallDispatchHeadNode callNode) {
468-
return callNode.call(string, "concat_internal", other);
477+
return callNode.call(string, "concat_internal", first);
469478
}
470479

480+
@ExplodeLoop
481+
@Specialization(guards = { "wasProvided(first)", "rest.length > 0", "rest.length == cachedLength" })
482+
protected Object concatMany(DynamicObject string, Object first, Object[] rest,
483+
@Cached("rest.length") int cachedLength,
484+
@Cached ConcatNode argConcatNode,
485+
@Cached("createBinaryProfile()") ConditionProfile selfArgProfile) {
486+
Rope rope = StringOperations.rope(string);
487+
Object result = argConcatNode.executeConcat(string, first, EMPTY_ARGUMENTS);
488+
for (int i = 0; i < cachedLength; ++i) {
489+
if (selfArgProfile.profile(rest[i] == string)) {
490+
Object copy = createString(getContext(), rope);
491+
result = argConcatNode.executeConcat(string, copy, EMPTY_ARGUMENTS);
492+
} else {
493+
result = argConcatNode.executeConcat(string, rest[i], EMPTY_ARGUMENTS);
494+
}
495+
}
496+
return result;
497+
}
498+
499+
/** Same implementation as {@link #concatMany}, safe for the use of {@code cachedLength} */
500+
@Specialization(guards = { "wasProvided(first)", "rest.length > 0" }, replaces = "concatMany")
501+
protected Object concatManyGeneral(DynamicObject string, Object first, Object[] rest,
502+
@Cached ConcatNode argConcatNode,
503+
@Cached("createBinaryProfile()") ConditionProfile selfArgProfile) {
504+
Rope rope = StringOperations.rope(string);
505+
Object result = argConcatNode.executeConcat(string, first, EMPTY_ARGUMENTS);
506+
for (Object arg : rest) {
507+
if (selfArgProfile.profile(arg == string)) {
508+
Object copy = createString(getContext(), rope);
509+
result = argConcatNode.executeConcat(string, copy, EMPTY_ARGUMENTS);
510+
} else {
511+
result = argConcatNode.executeConcat(string, arg, EMPTY_ARGUMENTS);
512+
}
513+
}
514+
return result;
515+
}
471516
}
472517

473518
@CoreMethod(

0 commit comments

Comments
 (0)