diff --git a/CHANGELOG.md b/CHANGELOG.md index dc36c2b18b85..f6d0b9000891 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,10 @@ Compatibility: * Allow `Range#include?` and `#member?` with `Time` (#2202, @wildmaples). * Implemented `Comparable#clamp(Range)` (#2200, @wildmaples). * Added a `Array#minmax` to override `Enumerable#minmax` (#2199, @wildmaples). +* Added beginless range support for `Range#{new, bsearch, count, each, equal_value, first, inspect, max, min, size, cover?, include?, ===}`. +* Added beginless range support for `Array#{[], []=, slice, slice!, to_a, fill, values_at}` (#2155, @LillianZ). +* Added beginless range support for `String#{byteslice, slice, slice!}` and `Symbol#slice` (#2211, @LillianZ). +* Added beginless range support for `Kernel#{caller, caller_locations}` and `Thread#backtrace_locations` (#2211, @LillianZ). Performance: diff --git a/spec/ruby/core/kernel/caller_locations_spec.rb b/spec/ruby/core/kernel/caller_locations_spec.rb index 8050b5b3abdc..bbb7e7b7370b 100644 --- a/spec/ruby/core/kernel/caller_locations_spec.rb +++ b/spec/ruby/core/kernel/caller_locations_spec.rb @@ -36,6 +36,14 @@ end end + ruby_version_is "2.7" do + it "works with beginless ranges" do + locations1 = caller_locations(0) + locations2 = caller_locations(eval("(...5)")) + locations2.map(&:to_s)[eval("(2..)")].should == locations1[eval("(...5)")].map(&:to_s)[eval("(2..)")] + end + end + it "can be called with a range whose end is negative" do locations1 = caller_locations(0) locations2 = caller_locations(2..-1) diff --git a/spec/ruby/core/kernel/caller_spec.rb b/spec/ruby/core/kernel/caller_spec.rb index 06e9ea6fc83c..6c175868cb84 100644 --- a/spec/ruby/core/kernel/caller_spec.rb +++ b/spec/ruby/core/kernel/caller_spec.rb @@ -52,6 +52,14 @@ end end + ruby_version_is "2.7" do + it "works with beginless ranges" do + locations1 = KernelSpecs::CallerTest.locations(0) + locations2 = KernelSpecs::CallerTest.locations(eval("(..5)")) + locations2.map(&:to_s)[eval("(2..)")].should == locations1[eval("(..5)")].map(&:to_s)[eval("(2..)")] + end + end + guard -> { Kernel.instance_method(:tap).source_location } do it "includes core library methods defined in Ruby" do file, line = Kernel.instance_method(:tap).source_location diff --git a/spec/ruby/core/range/shared/cover.rb b/spec/ruby/core/range/shared/cover.rb index 5b09cea4e08a..1de3395cd3e2 100644 --- a/spec/ruby/core/range/shared/cover.rb +++ b/spec/ruby/core/range/shared/cover.rb @@ -152,4 +152,46 @@ end end end + ruby_version_is "2.7" do + it "allows self to be a beginless range" do + eval("(...10)").send(@method, (3..7)).should be_true + eval("(...10)").send(@method, (3..15)).should be_false + + eval("(..7.9)").send(@method, (2.5..6.5)).should be_true + eval("(..7.9)").send(@method, (2.5..8.5)).should be_false + + eval("(..'i')").send(@method, ('d'..'f')).should be_true + eval("(..'i')").send(@method, ('d'..'z')).should be_false + end + + it "allows self to be a endless range" do + eval("(0...)").send(@method, (3..7)).should be_true + eval("(5...)").send(@method, (3..15)).should be_false + + eval("(1.1..)").send(@method, (2.5..6.5)).should be_true + eval("(3.3..)").send(@method, (2.5..8.5)).should be_false + + eval("('a'..)").send(@method, ('d'..'f')).should be_true + eval("('p'..)").send(@method, ('d'..'z')).should be_false + end + + it "accepts beginless range argument" do + eval("(..10)").send(@method, eval("(...10)")).should be_true + (0..10).send(@method, eval("(...10)")).should be_false + + (1.1..7.9).send(@method, eval("(...10.5)")).should be_false + + ('c'..'i').send(@method, eval("(..'i')")).should be_false + end + + it "accepts endless range argument" do + eval("(0..)").send(@method, eval("(0...)")).should be_true + (0..10).send(@method, eval("(0...)")).should be_false + + (1.1..7.9).send(@method, eval("(0.8...)")).should be_false + + ('c'..'i').send(@method, eval("('a'..)")).should be_false + end + + end end diff --git a/spec/ruby/core/range/shared/cover_and_include.rb b/spec/ruby/core/range/shared/cover_and_include.rb index b308524310f4..0267e3ccb0f2 100644 --- a/spec/ruby/core/range/shared/cover_and_include.rb +++ b/spec/ruby/core/range/shared/cover_and_include.rb @@ -26,6 +26,13 @@ end end + ruby_version_is "2.7" do + it "returns true if other is an element of self for beginless ranges" do + eval("(..10)").send(@method, 2.4).should == true + eval("(...10.5)").send(@method, 2.4).should == true + end + end + it "compares values using <=>" do rng = (1..5) m = mock("int") diff --git a/spec/ruby/core/range/to_s_spec.rb b/spec/ruby/core/range/to_s_spec.rb index ccbc5d8e7eb2..59672da82273 100644 --- a/spec/ruby/core/range/to_s_spec.rb +++ b/spec/ruby/core/range/to_s_spec.rb @@ -18,6 +18,13 @@ end end + ruby_version_is "2.7" do + it "can show beginless ranges" do + eval("(..1)").to_s.should == "..1" + eval("(...1.0)").to_s.should == "...1.0" + end + end + ruby_version_is ''...'2.7' do it "returns a tainted string if either end is tainted" do (("a".taint)..."c").to_s.tainted?.should be_true diff --git a/spec/ruby/core/string/shared/slice.rb b/spec/ruby/core/string/shared/slice.rb index 69997b7c1da0..a674e0b6efe6 100644 --- a/spec/ruby/core/string/shared/slice.rb +++ b/spec/ruby/core/string/shared/slice.rb @@ -335,6 +335,16 @@ "hello there".send(@method, eval("(-4...)")).should == "here" end end + + ruby_version_is "2.7" do + it "works with beginless ranges" do + "hello there".send(@method, eval("(..5)")).should == "hello " + "hello there".send(@method, eval("(...5)")).should == "hello" + "hello there".send(@method, eval("(..-4)")).should == "hello th" + "hello there".send(@method, eval("(...-4)")).should == "hello t" + "hello there".send(@method, eval("(...nil)")).should == "hello there" + end + end end describe :string_slice_regexp, shared: true do diff --git a/spec/ruby/core/thread/backtrace_locations_spec.rb b/spec/ruby/core/thread/backtrace_locations_spec.rb index ead4be2d8cd9..1f77e133789e 100644 --- a/spec/ruby/core/thread/backtrace_locations_spec.rb +++ b/spec/ruby/core/thread/backtrace_locations_spec.rb @@ -51,6 +51,14 @@ end end + ruby_version_is "2.7" do + it "can be called with an beginless range" do + locations1 = Thread.current.backtrace_locations(0) + locations2 = Thread.current.backtrace_locations(eval("(..5)")) + locations2.map(&:to_s)[eval("(2..)")].should == locations1[eval("(..5)")].map(&:to_s)[eval("(2..)")] + end + end + it "returns nil if omitting more locations than available" do Thread.current.backtrace_locations(100).should == nil Thread.current.backtrace_locations(100..-1).should == nil diff --git a/src/main/java/org/truffleruby/core/string/StringNodes.java b/src/main/java/org/truffleruby/core/string/StringNodes.java index 920b9536e45f..83d5fc8b1929 100644 --- a/src/main/java/org/truffleruby/core/string/StringNodes.java +++ b/src/main/java/org/truffleruby/core/string/StringNodes.java @@ -656,12 +656,25 @@ protected Object sliceEndlessRange(Object string, RubyObjectRange range, NotProv return sliceRange(string, libString, toLong(range.begin), stringEnd, range.excludedEnd); } + @Specialization(guards = "range.isBeginless()") + protected Object sliceBeginlessRange(Object string, RubyObjectRange range, NotProvided length, + @CachedLibrary(limit = "2") RubyStringLibrary libString) { + return sliceRange(string, libString, 0L, toLong(range.end), range.excludedEnd); + } + @Specialization(guards = "range.isBounded()") protected Object sliceObjectRange(Object string, RubyObjectRange range, NotProvided length, @CachedLibrary(limit = "2") RubyStringLibrary libString) { return sliceRange(string, libString, toLong(range.begin), toLong(range.end), range.excludedEnd); } + @Specialization(guards = "range.isBoundless()") + protected Object sliceBoundlessRange(Object string, RubyObjectRange range, NotProvided length, + @CachedLibrary(limit = "2") RubyStringLibrary libString) { + final int stringEnd = range.excludedEnd ? Integer.MAX_VALUE : Integer.MAX_VALUE - 1; + return sliceRange(string, libString, 0L, stringEnd, range.excludedEnd); + } + // endregion // region Range Slice Logic diff --git a/src/main/ruby/truffleruby/core/kernel.rb b/src/main/ruby/truffleruby/core/kernel.rb index d1c60d7c42c5..69200779d99b 100644 --- a/src/main/ruby/truffleruby/core/kernel.rb +++ b/src/main/ruby/truffleruby/core/kernel.rb @@ -726,17 +726,23 @@ def printf(*args) end def caller(start = 1, limit = nil) - args = if start.is_a? Range - if Primitive.nil? start.end - [start.begin + 1] - else - [start.begin + 1, start.size] - end - elsif Primitive.nil? limit - [start + 1] - else - [start + 1, limit] - end + args = if start.is_a? Range + if Primitive.nil?(start.begin) and Primitive.nil?(start.end) + [1] + elsif Primitive.nil? start.begin + size = start.end + 1 + size -= 1 if start.exclude_end? + [1, size] + elsif Primitive.nil? start.end + [start.begin + 1] + else + [start.begin + 1, start.size] + end + elsif Primitive.nil? limit + [start + 1] + else + [start + 1, limit] + end Kernel.caller_locations(*args).map(&:to_s) end module_function :caller diff --git a/src/main/ruby/truffleruby/core/truffle/kernel_operations.rb b/src/main/ruby/truffleruby/core/truffle/kernel_operations.rb index 0e8ea25db6d2..8904ea4b233b 100644 --- a/src/main/ruby/truffleruby/core/truffle/kernel_operations.rb +++ b/src/main/ruby/truffleruby/core/truffle/kernel_operations.rb @@ -181,7 +181,11 @@ def self.normalize_backtrace_args(omit, length) end if Range === omit range = omit - omit = Primitive.rb_to_int(range.begin) + if Primitive.nil? range.begin + omit = 0 + else + omit = Primitive.rb_to_int(range.begin) + end unless Primitive.nil? range.end end_index = Primitive.rb_to_int(range.end) if end_index < 0 diff --git a/src/main/ruby/truffleruby/core/truffle/range_operations.rb b/src/main/ruby/truffleruby/core/truffle/range_operations.rb index 3097f7cb73b7..ae71fe5ce1c2 100644 --- a/src/main/ruby/truffleruby/core/truffle/range_operations.rb +++ b/src/main/ruby/truffleruby/core/truffle/range_operations.rb @@ -74,66 +74,53 @@ def self.validate_step_size(first, last, step_size) # MRI: r_cover_range_p def self.range_cover?(range, other) - e = range.end - other_b = other.begin - other_e = other.end + b1 = range.begin + b2 = other.begin + e1 = range.end + e2 = other.end - return false if !Primitive.nil?(e) && Primitive.nil?(other_e) + return false if Primitive.nil?(b2) && !Primitive.nil?(b1) + return false if Primitive.nil?(e2) && !Primitive.nil?(e1) - return false if !Primitive.nil?(other_e) && - range_less(other_b, other_e) > (other.exclude_end? ? -1 : 0) + return false unless (Primitive.nil?(b2) || cover?(range, b2)) - return false unless cover?(range, other_b) + return true if Primitive.nil?(e2) - compare_end = range_less(e, other_e) - - if (range.exclude_end? && other.exclude_end?) || - (!range.exclude_end? && !other.exclude_end?) - return compare_end >= 0 - elsif range.exclude_end? - return compare_end > 0 - elsif compare_end >= 0 - return true + if e1 == e2 + !(range.exclude_end? && !other.exclude_end?) + else + if Integer === e2 && other.exclude_end? + cover?(range, e2 - 1) + else + cover?(range, e2) + end end - - other_max = begin - other.max - rescue TypeError - nil - end - return false if Primitive.nil?(other_max) - - range_less(e, other_max) >= 0 end - # MRI: r_cover_p + # # MRI: r_cover_p def self.cover?(range, value) - # MRI uses <=> to compare, so must we. - beg_compare = (range.begin <=> value) - return false unless beg_compare - - if Comparable.compare_int(beg_compare) <= 0 - return true if Primitive.nil? range.end - end_compare = (value <=> range.end) + # Check lower bound. + if !Primitive.nil?(range.begin) + # MRI uses <=> to compare, so must we. + cmp = (range.begin <=> value) + return false unless cmp + return false if Comparable.compare_int(cmp) > 0 + end + # Check upper bound. + if !Primitive.nil?(range.end) + cmp = (value <=> range.end) + if Primitive.nil? cmp + p range, value + end if range.exclude_end? - return true if Comparable.compare_int(end_compare) < 0 + return false if Comparable.compare_int(cmp) >= 0 else - return true if Comparable.compare_int(end_compare) <= 0 + return false if Comparable.compare_int(cmp) > 0 end end - false - end - - # MRI: r_less - def self.range_less(a, b) - compare = a <=> b - if Primitive.nil?(compare) - 1 - else - Comparable.compare_int(compare) - end + true end end end