Skip to content

Commit 5a119b7

Browse files
author
Nicolas Laurent
committed
implement []= with endless ranges
also add []= specs for endless ranges and make Range#new specs pass
1 parent 00a7ce4 commit 5a119b7

File tree

7 files changed

+73
-20
lines changed

7 files changed

+73
-20
lines changed

spec/ruby/core/array/element_set_spec.rb

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,14 @@ def obj.to_ary() [1, 2, 3] end
396396
a.should == [1, 2, 3, 8, 4, 5]
397397
end
398398

399+
it "inserts at the end if m > the array size" do
400+
a = [1, 2, 3]
401+
a[3..3] = [4]
402+
a.should == [1, 2, 3, 4]
403+
a[5..7] = [6]
404+
a.should == [1, 2, 3, 4, nil, 6]
405+
end
406+
399407
describe "Range subclasses" do
400408
before :each do
401409
@range_incl = ArraySpecs::MyRange.new(1, 2)
@@ -425,6 +433,43 @@ def obj.to_ary() [1, 2, 3] end
425433
end
426434
end
427435

436+
describe "Array#[]= with [m..]" do
437+
438+
it "just sets the section defined by range to nil even if the rhs is nil" do
439+
a = [1, 2, 3, 4, 5]
440+
a[2..] = nil
441+
a.should == [1, 2, nil]
442+
end
443+
444+
it "just sets the section defined by range to nil if m and n < 0 and the rhs is nil" do
445+
a = [1, 2, 3, 4, 5]
446+
a[-3..] = nil
447+
a.should == [1, 2, nil]
448+
end
449+
450+
it "replaces the section defined by range" do
451+
a = [6, 5, 4, 3, 2, 1]
452+
a[3...] = 9
453+
a.should == [6, 5, 4, 9]
454+
a[2..] = [7, 7, 7]
455+
a.should == [6, 5, 7, 7, 7]
456+
end
457+
458+
it "replaces the section if m and n < 0" do
459+
a = [1, 2, 3, 4, 5]
460+
a[-3..] = [7, 8, 9]
461+
a.should == [1, 2, 7, 8, 9]
462+
end
463+
464+
it "inserts at the end if m > the array size" do
465+
a = [1, 2, 3]
466+
a[3..] = [4]
467+
a.should == [1, 2, 3, 4]
468+
a[5..] = [6]
469+
a.should == [1, 2, 3, 4, nil, 6]
470+
end
471+
end
472+
428473
describe "Array#[] after a shift" do
429474
it "works for insertion" do
430475
a = [1,2]

spec/tags/core/range/new_tags.txt

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

src/main/java/org/truffleruby/core/array/ArrayIndexSetNode.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,13 +219,15 @@ protected Object setRange(DynamicObject array, DynamicObject range, Object value
219219
@Specialization(guards = { "isIntRange(range)", "!isRubyArray(value)" })
220220
protected Object setRangeWithNonArray(DynamicObject array, DynamicObject range, Object value,
221221
NotProvided unused) {
222+
// the fallback will recurse after converting the object to an array
222223
return fallback(array, range, value, unused);
223224
}
224225

225226
// array[start..end] = object_or_array (non-int range)
226227

227228
@Specialization(guards = { "!isIntRange(range)", "isRubyRange(range)" })
228229
protected Object setOtherRange(DynamicObject array, DynamicObject range, Object value, NotProvided unused) {
230+
// the fallback will recurse after converting the range to an int range
229231
return fallback(array, range, value, unused);
230232
}
231233

src/main/java/org/truffleruby/core/array/ArrayNodes.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,9 @@ protected Object fallback(DynamicObject array, Object index, Object length, Obje
278278
argumentNames = { "index_start_or_range", "length_or_value", "value" })
279279
public abstract static class IndexSetPrimitiveNode extends ArrayIndexSetNode {
280280

281+
// This primitive inherits from the same base as IndexSetNode and is called in its fallback.
282+
// Hence we need to avoid infinite recursion on fallback.
283+
281284
protected abstract RubyNode[] getArguments();
282285

283286
@Override

src/main/java/org/truffleruby/core/range/RangeNodes.java

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -398,32 +398,44 @@ protected Object toA(VirtualFrame frame, DynamicObject range) {
398398

399399
}
400400

401+
/**
402+
* Returns a conversion of the range into an int range, with regard to the supplied array
403+
* (the array is necessary to handle endless ranges).
404+
*/
401405
@Primitive(name = "range_to_int_range")
402406
public abstract static class ToIntRangeNode extends PrimitiveArrayArgumentsNode {
403407

404408
@Child private ToIntNode toIntNode;
405409

406410
@Specialization(guards = "isIntRange(range)")
407-
protected DynamicObject intRange(DynamicObject range) {
411+
protected DynamicObject intRange(DynamicObject range, DynamicObject array) {
408412
return range;
409413
}
410414

411415
@Specialization(guards = "isLongRange(range)")
412-
protected DynamicObject longRange(DynamicObject range) {
416+
protected DynamicObject longRange(DynamicObject range, DynamicObject array) {
413417
int begin = toInt(Layouts.LONG_RANGE.getBegin(range));
414418
int end = toInt(Layouts.LONG_RANGE.getEnd(range));
415419
boolean excludedEnd = Layouts.LONG_RANGE.getExcludedEnd(range);
416420
return Layouts.INT_RANGE.createIntRange(coreLibrary().intRangeFactory, excludedEnd, begin, end);
417421
}
418422

419-
@Specialization(guards = "isObjectRange(range)")
420-
protected DynamicObject objectRange(DynamicObject range) {
423+
@Specialization(guards = { "isObjectRange(range)", "!isEndlessRange(getContext(), range)" })
424+
protected DynamicObject boundedObjectRange(DynamicObject range, DynamicObject array) {
421425
int begin = toInt(Layouts.OBJECT_RANGE.getBegin(range));
422426
int end = toInt(Layouts.OBJECT_RANGE.getEnd(range));
423427
boolean excludedEnd = Layouts.OBJECT_RANGE.getExcludedEnd(range);
424428
return Layouts.INT_RANGE.createIntRange(coreLibrary().intRangeFactory, excludedEnd, begin, end);
425429
}
426430

431+
@Specialization(guards = { "isObjectRange(range)", "isEndlessRange(getContext(), range)" })
432+
protected DynamicObject endlessObjectRange(DynamicObject range, DynamicObject array) {
433+
int begin = toInt(Layouts.OBJECT_RANGE.getBegin(range));
434+
int end = Layouts.ARRAY.getSize(array);
435+
boolean excludedEnd = true;
436+
return Layouts.INT_RANGE.createIntRange(coreLibrary().intRangeFactory, excludedEnd, begin, end);
437+
}
438+
427439
private int toInt(Object indexObject) {
428440
if (toIntNode == null) {
429441
CompilerDirectives.transferToInterpreterAndInvalidate();
@@ -479,8 +491,6 @@ protected DynamicObject longRange(DynamicObject rubyClass, long begin, long end,
479491
return Layouts.LONG_RANGE.createLongRange(coreLibrary().longRangeFactory, excludeEnd, begin, end);
480492
}
481493

482-
// TODO add specialization for nil end
483-
484494
@Specialization(guards = { "rubyClass != rangeClass || (!isIntOrLong(begin) || !isIntOrLong(end))" })
485495
protected Object objectRange(
486496
VirtualFrame frame,
@@ -497,17 +507,10 @@ protected Object objectRange(
497507
allocateNode = insert(AllocateObjectNode.create());
498508
}
499509

500-
final Object cmpResult;
501-
try {
502-
cmpResult = cmpNode.call(begin, "<=>", end);
503-
} catch (RaiseException e) {
510+
if (cmpNode.call(begin, "<=>", end) == nil() && end != nil()) {
504511
throw new RaiseException(getContext(), coreExceptions().argumentError("bad value for range", this));
505512
}
506513

507-
// if (cmpResult == nil()) { // TODO this throws
508-
// throw new RaiseException(getContext(), coreExceptions().argumentError("bad value for range", this));
509-
// }
510-
511514
return allocateNode.allocate(rubyClass, excludeEnd, begin, end);
512515
}
513516

src/main/java/org/truffleruby/language/RubyGuards.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ public static boolean isObjectRange(DynamicObject object) {
127127
return Layouts.OBJECT_RANGE.isObjectRange(object);
128128
}
129129

130+
public static boolean isEndlessRange(RubyContext context, DynamicObject object) {
131+
assert isObjectRange(object);
132+
return isNil(context, Layouts.OBJECT_RANGE.getEnd(object));
133+
}
134+
130135
public static boolean isRubyRange(Object value) {
131136
return isIntRange(value) || isLongRange(value) || isObjectRange(value);
132137
}

src/main/ruby/truffleruby/core/array.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def ==(other)
184184
if TrufflePrimitive.undefined?(value)
185185
value = length
186186
if Range === index
187-
index = TrufflePrimitive.range_to_int_range(index)
187+
index = TrufflePrimitive.range_to_int_range(index, self)
188188
converted = Array.try_convert(value)
189189
converted = [value] unless converted
190190
TrufflePrimitive.array_aset(self, index, converted, undefined)

0 commit comments

Comments
 (0)