From 26127b154b6b66fa1b7ca9286cf436461ec4f660 Mon Sep 17 00:00:00 2001 From: Lillian Zhang Date: Tue, 5 Jan 2021 17:39:45 -0500 Subject: [PATCH 01/12] add specs for range#cover? --- spec/ruby/core/range/shared/cover.rb | 42 +++++++++++++++++++ .../core/range/shared/cover_and_include.rb | 7 ++++ 2 files changed, 49 insertions(+) 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") From 8871c0b65c0b084943b7687327493ec2cbe17fe4 Mon Sep 17 00:00:00 2001 From: Lillian Zhang Date: Tue, 5 Jan 2021 18:21:06 -0500 Subject: [PATCH 02/12] Add beginless support for range#cover? --- .../truffleruby/core/truffle/range_operations.rb | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/ruby/truffleruby/core/truffle/range_operations.rb b/src/main/ruby/truffleruby/core/truffle/range_operations.rb index 3097f7cb73b7..a93b018b273a 100644 --- a/src/main/ruby/truffleruby/core/truffle/range_operations.rb +++ b/src/main/ruby/truffleruby/core/truffle/range_operations.rb @@ -79,11 +79,10 @@ def self.range_cover?(range, other) other_e = other.end return false if !Primitive.nil?(e) && Primitive.nil?(other_e) - - return false if !Primitive.nil?(other_e) && + return false if !Primitive.nil?(range.begin) && Primitive.nil?(other_b) + return false if !Primitive.nil?(other_b) && !Primitive.nil?(other_e) && range_less(other_b, other_e) > (other.exclude_end? ? -1 : 0) - - return false unless cover?(range, other_b) + return false if !Primitive.nil?(other_b) && !cover?(range, other_b) compare_end = range_less(e, other_e) @@ -110,7 +109,11 @@ def self.range_cover?(range, other) def self.cover?(range, value) # MRI uses <=> to compare, so must we. beg_compare = (range.begin <=> value) - return false unless beg_compare + + unless beg_compare + return false if range.begin # not beginless + beg_compare = -1 + end if Comparable.compare_int(beg_compare) <= 0 return true if Primitive.nil? range.end From 1beae8743f67ff4e580cd032bfe3a8a778ccb78b Mon Sep 17 00:00:00 2001 From: Lillian Zhang Date: Tue, 5 Jan 2021 18:50:05 -0500 Subject: [PATCH 03/12] Add spec for beginless range#to_s --- spec/ruby/core/range/to_s_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) 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 From cc13a677ea76a928c417146c7ccb0413548a39dc Mon Sep 17 00:00:00 2001 From: Lillian Zhang Date: Wed, 6 Jan 2021 09:32:31 -0500 Subject: [PATCH 04/12] Add specs for beginless range slices of Strings --- spec/ruby/core/string/shared/slice.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) 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 From e4a3f1e0cb257023e0f375ca8439134c1fe01073 Mon Sep 17 00:00:00 2001 From: Lillian Zhang Date: Wed, 6 Jan 2021 09:33:14 -0500 Subject: [PATCH 05/12] Add support for beginless range slices of Strings --- .../org/truffleruby/core/string/StringNodes.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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 From 5acd318b84bc9afca894f8144af2f9467f83a236 Mon Sep 17 00:00:00 2001 From: Lillian Zhang Date: Wed, 6 Jan 2021 16:16:34 -0500 Subject: [PATCH 06/12] Add specs for beginless ranges in caller and backtrace --- spec/ruby/core/kernel/caller_locations_spec.rb | 8 ++++++++ spec/ruby/core/kernel/caller_spec.rb | 8 ++++++++ spec/ruby/core/thread/backtrace_locations_spec.rb | 8 ++++++++ 3 files changed, 24 insertions(+) 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/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 From 3f0370b2691ef8bec4dfec6e88503dea4dc95832 Mon Sep 17 00:00:00 2001 From: Lillian Zhang Date: Wed, 6 Jan 2021 16:17:43 -0500 Subject: [PATCH 07/12] Add support for beginless ranges in caller and backtrace --- src/main/ruby/truffleruby/core/kernel.rb | 28 +++++++++++-------- .../core/truffle/kernel_operations.rb | 6 +++- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/main/ruby/truffleruby/core/kernel.rb b/src/main/ruby/truffleruby/core/kernel.rb index d1c60d7c42c5..d87be8440c15 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 From 63b96580d723f012772ef39d57d1df890c136e88 Mon Sep 17 00:00:00 2001 From: Lillian Zhang Date: Wed, 6 Jan 2021 16:36:41 -0500 Subject: [PATCH 08/12] Changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) 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: From ac9296038c00a12f28230dd839d4d1fefc7e22bd Mon Sep 17 00:00:00 2001 From: Lillian Zhang Date: Thu, 7 Jan 2021 15:51:37 -0500 Subject: [PATCH 09/12] Fix minor stylistic () --- src/main/ruby/truffleruby/core/kernel.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/ruby/truffleruby/core/kernel.rb b/src/main/ruby/truffleruby/core/kernel.rb index d87be8440c15..69200779d99b 100644 --- a/src/main/ruby/truffleruby/core/kernel.rb +++ b/src/main/ruby/truffleruby/core/kernel.rb @@ -727,13 +727,13 @@ def printf(*args) def caller(start = 1, limit = nil) args = if start.is_a? Range - if (Primitive.nil? start.begin) and (Primitive.nil? start.end) + if Primitive.nil?(start.begin) and Primitive.nil?(start.end) [1] - elsif (Primitive.nil? start.begin) + elsif Primitive.nil? start.begin size = start.end + 1 size -= 1 if start.exclude_end? [1, size] - elsif (Primitive.nil? start.end) + elsif Primitive.nil? start.end [start.begin + 1] else [start.begin + 1, start.size] From 8f8eaea592578656f95343661766fc585063f30c Mon Sep 17 00:00:00 2001 From: Lillian Zhang Date: Thu, 7 Jan 2021 17:23:02 -0500 Subject: [PATCH 10/12] Use more elegant implementations of RangeOperations#{range_cover?, cover?} --- .../core/truffle/range_operations.rb | 94 +++++++++---------- 1 file changed, 44 insertions(+), 50 deletions(-) diff --git a/src/main/ruby/truffleruby/core/truffle/range_operations.rb b/src/main/ruby/truffleruby/core/truffle/range_operations.rb index a93b018b273a..2c97d4b73849 100644 --- a/src/main/ruby/truffleruby/core/truffle/range_operations.rb +++ b/src/main/ruby/truffleruby/core/truffle/range_operations.rb @@ -74,69 +74,63 @@ 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 - - return false if !Primitive.nil?(e) && Primitive.nil?(other_e) - return false if !Primitive.nil?(range.begin) && Primitive.nil?(other_b) - return false if !Primitive.nil?(other_b) && !Primitive.nil?(other_e) && - range_less(other_b, other_e) > (other.exclude_end? ? -1 : 0) - return false if !Primitive.nil?(other_b) && !cover?(range, other_b) - - 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 - end + b1 = range.begin + b2 = other.begin + e1 = range.end + e2 = other.end - other_max = begin - other.max - rescue TypeError - nil - end - return false if Primitive.nil?(other_max) + return false if Primitive.nil?(b2) && !Primitive.nil?(b1) + return false if Primitive.nil?(e2) && !Primitive.nil?(e1) - range_less(e, other_max) >= 0 - end + return false unless (Primitive.nil?(b2) || cover?(range, b2)) - # MRI: r_cover_p - def self.cover?(range, value) - # MRI uses <=> to compare, so must we. - beg_compare = (range.begin <=> value) + return true if Primitive.nil?(e2) - unless beg_compare - return false if range.begin # not beginless - beg_compare = -1 + 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 + end - if Comparable.compare_int(beg_compare) <= 0 - return true if Primitive.nil? range.end - end_compare = (value <=> range.end) + # # MRI: r_cover_p + def self.cover?(range, value) + # 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 + true end - # MRI: r_less - def self.range_less(a, b) - compare = a <=> b - if Primitive.nil?(compare) - 1 - else - Comparable.compare_int(compare) - end - end + # # MRI: r_less + # def self.range_less(a, b) + # compare = a <=> b + # if Primitive.nil?(compare) + # 1 + # else + # Comparable.compare_int(compare) + # end + # end end end From c310ecbacd44f7668312f50c277b525fc0c7405e Mon Sep 17 00:00:00 2001 From: Lillian Zhang Date: Fri, 8 Jan 2021 09:08:01 -0500 Subject: [PATCH 11/12] Remove RangeOperations#range_less (MRI: r_less) method --- .../ruby/truffleruby/core/truffle/range_operations.rb | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/main/ruby/truffleruby/core/truffle/range_operations.rb b/src/main/ruby/truffleruby/core/truffle/range_operations.rb index 2c97d4b73849..d0be7c1c4a6b 100644 --- a/src/main/ruby/truffleruby/core/truffle/range_operations.rb +++ b/src/main/ruby/truffleruby/core/truffle/range_operations.rb @@ -122,15 +122,5 @@ def self.cover?(range, value) true end - - # # MRI: r_less - # def self.range_less(a, b) - # compare = a <=> b - # if Primitive.nil?(compare) - # 1 - # else - # Comparable.compare_int(compare) - # end - # end end end From faaeba6a738e1568193497c36e7ae916492d6b8a Mon Sep 17 00:00:00 2001 From: Lillian Zhang Date: Mon, 11 Jan 2021 15:35:38 -0500 Subject: [PATCH 12/12] rubocop --- src/main/ruby/truffleruby/core/truffle/range_operations.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/ruby/truffleruby/core/truffle/range_operations.rb b/src/main/ruby/truffleruby/core/truffle/range_operations.rb index d0be7c1c4a6b..ae71fe5ce1c2 100644 --- a/src/main/ruby/truffleruby/core/truffle/range_operations.rb +++ b/src/main/ruby/truffleruby/core/truffle/range_operations.rb @@ -82,7 +82,7 @@ def self.range_cover?(range, other) return false if Primitive.nil?(b2) && !Primitive.nil?(b1) return false if Primitive.nil?(e2) && !Primitive.nil?(e1) - return false unless (Primitive.nil?(b2) || cover?(range, b2)) + return false unless (Primitive.nil?(b2) || cover?(range, b2)) return true if Primitive.nil?(e2) @@ -90,7 +90,7 @@ def self.range_cover?(range, other) !(range.exclude_end? && !other.exclude_end?) else if Integer === e2 && other.exclude_end? - cover?(range, e2 -1) + cover?(range, e2 - 1) else cover?(range, e2) end