ruby-next-core 0.10.5 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -0
  3. data/README.md +25 -16
  4. data/lib/.rbnext/2.1/ruby-next/core.rb +206 -0
  5. data/lib/.rbnext/2.1/ruby-next/language.rb +227 -0
  6. data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +2 -2
  7. data/lib/.rbnext/2.3/ruby-next/language/eval.rb +4 -4
  8. data/lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb +23 -5
  9. data/lib/.rbnext/2.3/ruby-next/language/rewriters/endless_range.rb +1 -1
  10. data/lib/.rbnext/2.3/ruby-next/language/rewriters/pattern_matching.rb +20 -17
  11. data/lib/.rbnext/2.3/ruby-next/utils.rb +1 -1
  12. data/lib/.rbnext/2.7/ruby-next/core.rb +206 -0
  13. data/lib/ruby-next/commands/nextify.rb +1 -1
  14. data/lib/ruby-next/config.rb +50 -0
  15. data/lib/ruby-next/core/array/deconstruct.rb +1 -1
  16. data/lib/ruby-next/core/array/intersect.rb +9 -0
  17. data/lib/ruby-next/core/constants/frozen_error.rb +15 -0
  18. data/lib/ruby-next/core/constants/no_matching_pattern_error.rb +1 -1
  19. data/lib/ruby-next/core/enumerable/tally.rb +46 -7
  20. data/lib/ruby-next/core/hash/deconstruct_keys.rb +1 -1
  21. data/lib/ruby-next/core/struct/deconstruct_keys.rb +3 -3
  22. data/lib/ruby-next/core.rb +8 -4
  23. data/lib/ruby-next/core_ext.rb +3 -1
  24. data/lib/ruby-next/language/bootsnap.rb +1 -1
  25. data/lib/ruby-next/language/edge.rb +0 -6
  26. data/lib/ruby-next/language/eval.rb +3 -3
  27. data/lib/ruby-next/language/parser.rb +16 -0
  28. data/lib/ruby-next/language/rewriters/args_forward.rb +14 -6
  29. data/lib/ruby-next/language/rewriters/args_forward_leading.rb +75 -0
  30. data/lib/ruby-next/language/rewriters/base.rb +19 -1
  31. data/lib/ruby-next/language/rewriters/in_pattern.rb +56 -0
  32. data/lib/ruby-next/language/rewriters/method_reference.rb +1 -1
  33. data/lib/ruby-next/language/rewriters/numeric_literals.rb +41 -0
  34. data/lib/ruby-next/language/rewriters/pattern_matching.rb +18 -15
  35. data/lib/ruby-next/language/rewriters/required_kwargs.rb +39 -0
  36. data/lib/ruby-next/language/rewriters/safe_navigation.rb +42 -29
  37. data/lib/ruby-next/language/rewriters/shorthand_hash.rb +1 -1
  38. data/lib/ruby-next/language/setup.rb +7 -4
  39. data/lib/ruby-next/language/unparser.rb +5 -0
  40. data/lib/ruby-next/language.rb +62 -44
  41. data/lib/ruby-next/rubocop.rb +9 -18
  42. data/lib/ruby-next/setup_self.rb +5 -3
  43. data/lib/ruby-next/version.rb +1 -1
  44. data/lib/ruby-next.rb +5 -38
  45. metadata +20 -18
  46. data/lib/.rbnext/2.3/ruby-next/language/rewriters/right_hand_assignment.rb +0 -117
  47. data/lib/ruby-next/language/rewriters/right_hand_assignment.rb +0 -117
@@ -60,7 +60,7 @@ module RubyNext
60
60
  eval_mid = Kernel.respond_to?(:eval_without_ruby_next) ? :eval_without_ruby_next : :eval
61
61
  Kernel.send eval_mid, self::SYNTAX_PROBE, nil, __FILE__, __LINE__
62
62
  false
63
- rescue SyntaxError, NameError
63
+ rescue SyntaxError, StandardError
64
64
  true
65
65
  ensure
66
66
  $VERBOSE = save_verbose
@@ -93,20 +93,38 @@ module RubyNext
93
93
 
94
94
  private
95
95
 
96
+ # BFS with predicate block
97
+ def find_child(node)
98
+ queue = [node]
99
+
100
+ loop do
101
+ break if queue.empty?
102
+
103
+ child = queue.shift
104
+ next unless child.is_a?(::Parser::AST::Node)
105
+
106
+ return child if yield child
107
+
108
+ queue.push(*child.children)
109
+ end
110
+
111
+ nil
112
+ end
113
+
96
114
  def replace(range, ast)
97
- ((!@source_rewriter.nil? || nil) && @source_rewriter.replace(range, unparse(ast)))
115
+ ((((__safe_lvar__ = @source_rewriter) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.replace(range, unparse(ast)))
98
116
  end
99
117
 
100
118
  def remove(range)
101
- ((!@source_rewriter.nil? || nil) && @source_rewriter.remove(range))
119
+ ((((__safe_lvar__ = @source_rewriter) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.remove(range))
102
120
  end
103
121
 
104
122
  def insert_after(range, ast)
105
- ((!@source_rewriter.nil? || nil) && @source_rewriter.insert_after(range, unparse(ast)))
123
+ ((((__safe_lvar__ = @source_rewriter) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.insert_after(range, unparse(ast)))
106
124
  end
107
125
 
108
126
  def insert_before(range, ast)
109
- ((!@source_rewriter.nil? || nil) && @source_rewriter.insert_before(range, unparse(ast)))
127
+ ((((__safe_lvar__ = @source_rewriter) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.insert_before(range, unparse(ast)))
110
128
  end
111
129
 
112
130
  def unparse(ast)
@@ -55,7 +55,7 @@ module RubyNext
55
55
  attr_reader :current_index
56
56
 
57
57
  def index_arg?(node)
58
- ((!((!current_index.nil? || nil) && current_index.children).nil? || nil) && ((!current_index.nil? || nil) && current_index.children).include?(node))
58
+ ((((__safe_lvar__ = ((((__safe_lvar__ = current_index) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.children)) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.include?(node))
59
59
  end
60
60
  end
61
61
  end
@@ -13,12 +13,12 @@ module RubyNext
13
13
 
14
14
  # Useful to generate simple operation nodes
15
15
  # (e.g., 'a + b')
16
- def -(val)
17
- ::Parser::AST::Node.new(:send, [self, :-, val.to_ast_node])
16
+ def -(other)
17
+ ::Parser::AST::Node.new(:send, [self, :-, other.to_ast_node])
18
18
  end
19
19
 
20
- def +(val)
21
- ::Parser::AST::Node.new(:send, [self, :+, val.to_ast_node])
20
+ def +(other)
21
+ ::Parser::AST::Node.new(:send, [self, :+, other.to_ast_node])
22
22
  end
23
23
  end
24
24
 
@@ -218,9 +218,10 @@ module RubyNext
218
218
  predicate_clause(:respond_to_deconstruct_keys, node)
219
219
  end
220
220
 
221
- def hash_key(node, key)
222
- key = key.children.first if key.is_a?(::Parser::AST::Node)
223
- predicate_clause(:"hash_key_#{key}", node)
221
+ def hash_keys(node, keys)
222
+ keys = keys.map { |key| key.is_a?(::Parser::AST::Node) ? key.children.first : key }
223
+
224
+ predicate_clause(:"hash_keys_#{keys.join("_p_")}", node)
224
225
  end
225
226
  end
226
227
  end
@@ -266,7 +267,7 @@ module RubyNext
266
267
  )
267
268
  end
268
269
 
269
- def on_in_match(node)
270
+ def on_match_pattern(node)
270
271
  context.track! self
271
272
 
272
273
  @deconstructed_keys = {}
@@ -303,6 +304,8 @@ module RubyNext
303
304
  end
304
305
  end
305
306
 
307
+ alias on_in_match on_match_pattern
308
+
306
309
  private
307
310
 
308
311
  def rewrite_case_in!(node, matchee, new_node)
@@ -310,7 +313,7 @@ module RubyNext
310
313
  remove(node.children[0].loc.expression)
311
314
 
312
315
  node.children[1..-1].each.with_index do |clause, i|
313
- if ((!clause.nil? || nil) && clause.type) == :in_pattern
316
+ if ((((__safe_lvar__ = clause) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.type) == :in_pattern
314
317
  # handle multiline clauses differently
315
318
  if clause.loc.last_line > clause.children[0].loc.last_line + 1
316
319
  height = clause.loc.last_line - clause.children[0].loc.last_line
@@ -341,7 +344,7 @@ module RubyNext
341
344
  clauses = []
342
345
 
343
346
  nodes.each do |clause|
344
- if ((!clause.nil? || nil) && clause.type) == :in_pattern
347
+ if ((((__safe_lvar__ = clause) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.type) == :in_pattern
345
348
  clauses << build_when_clause(clause)
346
349
  else
347
350
  else_clause = process(clause)
@@ -350,7 +353,7 @@ module RubyNext
350
353
 
351
354
  else_clause = (else_clause || no_matching_pattern).then do |node|
352
355
  next node unless node.type == :empty_else
353
- s(:empty)
356
+ nil
354
357
  end
355
358
 
356
359
  clauses << else_clause
@@ -883,14 +886,14 @@ module RubyNext
883
886
  end
884
887
 
885
888
  def having_hash_keys(keys, hash = s(:lvar, locals[:hash]))
886
- key = keys.shift
887
- node = predicates.hash_key(hash_has_key(key, hash), key)
889
+ keys.reduce(nil) do |acc, key|
890
+ pnode = hash_has_key(key, hash)
891
+ next pnode unless acc
888
892
 
889
- keys.reduce(node) do |res, key|
890
893
  s(:begin,
891
- s(:and,
892
- res,
893
- predicates.hash_key(hash_has_key(key, hash), key)))
894
+ s(:and, acc, pnode))
895
+ end.then do |node|
896
+ predicates.hash_keys(node, keys)
894
897
  end
895
898
  end
896
899
 
@@ -6,7 +6,7 @@ module RubyNext
6
6
 
7
7
  if $LOAD_PATH.respond_to?(:resolve_feature_path)
8
8
  def resolve_feature_path(feature)
9
- ((!$LOAD_PATH.resolve_feature_path(feature).nil? || nil) && $LOAD_PATH.resolve_feature_path(feature).last)
9
+ ((((__safe_lvar__ = $LOAD_PATH.resolve_feature_path(feature)) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.last)
10
10
  rescue LoadError
11
11
  end
12
12
  else
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ require "ruby-next/config"
6
+ require "ruby-next/utils"
7
+
8
+ module RubyNext
9
+ module Core
10
+ # Patch contains the extension implementation
11
+ # and meta information (e.g., Ruby version).
12
+ class Patch
13
+ attr_reader :refineables, :name, :mod, :method_name, :version, :body, :singleton, :core_ext, :supported, :native, :location
14
+
15
+ # Create a new patch for module/class (mod)
16
+ # with the specified uniq name
17
+ #
18
+ # `core_ext` defines the strategy for core extensions:
19
+ # - :patch — extend class directly
20
+ # - :prepend — extend class by prepending a module (e.g., when needs `super`)
21
+ def initialize(mod = nil, method:, version:, name: nil, supported: nil, native: nil, location: nil, refineable: mod, core_ext: :patch, singleton: nil)
22
+ @mod = mod
23
+ @method_name = method
24
+ @version = version
25
+ if method_name && mod
26
+ @supported = supported.nil? ? mod.method_defined?(method_name) : supported
27
+ # define whether running Ruby has a native implementation for this method
28
+ # for that, we check the source_location (which is nil for C defined methods)
29
+ @native = native.nil? ? (supported? && native_location?(mod.instance_method(method_name).source_location)) : native
30
+ end
31
+ @singleton = singleton
32
+ @refineables = Array(refineable)
33
+ @body = yield
34
+ @core_ext = core_ext
35
+ @location = location || build_location(caller_locations(1, 5))
36
+ @name = name || build_module_name
37
+ end
38
+
39
+ def prepend?
40
+ core_ext == :prepend
41
+ end
42
+
43
+ def core_ext?
44
+ !mod.nil?
45
+ end
46
+
47
+ alias supported? supported
48
+ alias native? native
49
+ alias singleton? singleton
50
+
51
+ def to_module
52
+ Module.new.tap do |ext|
53
+ ext.module_eval(body, *location)
54
+
55
+ RubyNext::Core.const_set(name, ext)
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def build_module_name
62
+ mod_name = singleton? ? singleton.name : mod.name
63
+ camelized_method_name = method_name.to_s.split("_").map(&:capitalize).join
64
+
65
+ "#{mod_name}#{camelized_method_name}".gsub(/\W/, "")
66
+ end
67
+
68
+ def build_location(trace_locations)
69
+ # The caller_locations behaviour depends on implementaion,
70
+ # e.g. in JRuby https://github.com/jruby/jruby/issues/6055
71
+ while trace_locations.first.label != "patch"
72
+ trace_locations.shift
73
+ end
74
+
75
+ trace_location = trace_locations[1]
76
+
77
+ [trace_location.absolute_path, trace_location.lineno + 2]
78
+ end
79
+
80
+ def native_location?(location)
81
+ location.nil? || location.first.match?(/(<internal:|resource:\/truffleruby\/core)/)
82
+ end
83
+ end
84
+
85
+ # Registry for patches
86
+ class Patches
87
+ attr_reader :extensions, :refined
88
+
89
+ def initialize
90
+ @names = Set.new
91
+ @extensions = Hash.new { |h, k| h[k] = [] }
92
+ @refined = Hash.new { |h, k| h[k] = [] }
93
+ end
94
+
95
+ # Register new patch
96
+ def <<(patch)
97
+ raise ArgumentError, "Patch already registered: #{patch.name}" if @names.include?(patch.name)
98
+ @names << patch.name
99
+ @extensions[patch.mod] << patch if patch.core_ext?
100
+ patch.refineables.each { |r| @refined[r] << patch } unless patch.native?
101
+ end
102
+ end
103
+
104
+ class << self
105
+ STRATEGIES = %i[refine core_ext backports].freeze
106
+
107
+ attr_reader :strategy
108
+
109
+ def strategy=(val)
110
+ raise ArgumentError, "Unknown strategy: #{val}. Available: #{STRATEGIES.join(",")}" unless STRATEGIES.include?(val)
111
+ @strategy = val
112
+ end
113
+
114
+ def refine?
115
+ strategy == :refine
116
+ end
117
+
118
+ def core_ext?
119
+ strategy == :core_ext || strategy == :backports
120
+ end
121
+
122
+ def backports?
123
+ strategy == :backports
124
+ end
125
+
126
+ def patch(*__rest__, &__block__)
127
+ patches << Patch.new(*__rest__, &__block__)
128
+ end
129
+
130
+ # Inject `using RubyNext` at the top of the source code
131
+ def inject!(contents)
132
+ if contents.frozen?
133
+ contents = contents.sub(/^(\s*[^#\s].*)/, 'using RubyNext;\1')
134
+ else
135
+ contents.sub!(/^(\s*[^#\s].*)/, 'using RubyNext;\1')
136
+ end
137
+ contents
138
+ end
139
+
140
+ def patches
141
+ @patches ||= Patches.new
142
+ end
143
+ end
144
+
145
+ # Use refinements by default
146
+ self.strategy = ENV.fetch("RUBY_NEXT_CORE_STRATEGY", "refine").to_sym
147
+ end
148
+ end
149
+
150
+ require "backports/2.5" if RubyNext::Core.backports?
151
+
152
+ require "ruby-next/core/kernel/then"
153
+
154
+ require "ruby-next/core/proc/compose"
155
+
156
+ require "ruby-next/core/enumerable/tally"
157
+ require "ruby-next/core/enumerable/filter"
158
+ require "ruby-next/core/enumerable/filter_map"
159
+
160
+ require "ruby-next/core/enumerator/produce"
161
+
162
+ require "ruby-next/core/array/difference_union_intersection"
163
+
164
+ require "ruby-next/core/hash/merge"
165
+
166
+ require "ruby-next/core/string/split"
167
+
168
+ require "ruby-next/core/symbol/start_with"
169
+ require "ruby-next/core/symbol/end_with"
170
+
171
+ require "ruby-next/core/unboundmethod/bind_call"
172
+
173
+ require "ruby-next/core/time/floor"
174
+ require "ruby-next/core/time/ceil"
175
+
176
+ # Core extensions required for pattern matching
177
+ # Required for pattern matching with refinements
178
+ unless defined?(NoMatchingPatternError)
179
+ class NoMatchingPatternError < StandardError
180
+ end
181
+ end
182
+
183
+ require "ruby-next/core/constants/no_matching_pattern_error"
184
+ require "ruby-next/core/constants/frozen_error"
185
+ require "ruby-next/core/array/deconstruct"
186
+ require "ruby-next/core/hash/deconstruct_keys"
187
+ require "ruby-next/core/struct/deconstruct"
188
+ require "ruby-next/core/struct/deconstruct_keys"
189
+
190
+ require "ruby-next/core/hash/except"
191
+
192
+ require "ruby-next/core/array/intersect"
193
+
194
+ # Generate refinements
195
+ RubyNext.module_eval do
196
+ RubyNext::Core.patches.refined.each do |mod, patches|
197
+ # Only refine modules when supported
198
+ next unless mod.is_a?(Class) || RubyNext::Utils.refine_modules?
199
+
200
+ refine mod do
201
+ patches.each do |patch|
202
+ module_eval(patch.body, *patch.location)
203
+ end
204
+ end
205
+ end
206
+ end
@@ -57,7 +57,7 @@ module RubyNext
57
57
 
58
58
  opts.on(
59
59
  "--transpile-mode=MODE",
60
- "Transpiler mode (ast or rewrite). Default: ast"
60
+ "Transpiler mode (ast or rewrite). Default: rewrite"
61
61
  ) do |val|
62
62
  Language.mode = val.to_sym
63
63
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ # Mininum Ruby version supported by RubyNext
5
+ MIN_SUPPORTED_VERSION = Gem::Version.new("2.2.0")
6
+
7
+ # Where to store transpiled files (relative from the project LOAD_PATH, usually `lib/`)
8
+ RUBY_NEXT_DIR = ".rbnext"
9
+
10
+ # Defines last minor version for every major version
11
+ LAST_MINOR_VERSIONS = {
12
+ 2 => 8, # 2.8 is required for backward compatibility: some gems already uses it
13
+ 3 => 0
14
+ }.freeze
15
+
16
+ LATEST_VERSION = [3, 0].freeze
17
+
18
+ # A virtual version number used for proposed features
19
+ NEXT_VERSION = "1995.next.0"
20
+
21
+ class << self
22
+ # TruffleRuby claims it's 2.7.2 compatible but...
23
+ if defined?(TruffleRuby) && ::RUBY_VERSION =~ /^2\.7/
24
+ def current_ruby_version
25
+ "2.6.5"
26
+ end
27
+ else
28
+ def current_ruby_version
29
+ ::RUBY_VERSION
30
+ end
31
+ end
32
+
33
+ def next_ruby_version(version = current_ruby_version)
34
+ return if version == Gem::Version.new(NEXT_VERSION)
35
+
36
+ major, minor = Gem::Version.new(version).segments.map(&:to_i)
37
+
38
+ return Gem::Version.new(NEXT_VERSION) if major >= LATEST_VERSION.first && minor >= LATEST_VERSION.last
39
+
40
+ nxt =
41
+ if LAST_MINOR_VERSIONS[major] == minor
42
+ "#{major + 1}.0.0"
43
+ else
44
+ "#{major}.#{minor + 1}.0"
45
+ end
46
+
47
+ Gem::Version.new(nxt)
48
+ end
49
+ end
50
+ end
@@ -9,7 +9,7 @@ end
9
9
  end
10
10
 
11
11
  # We need to hack `respond_to?` in Ruby 2.5, since it's not working with refinements
12
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.6")
12
+ if Gem::Version.new(::RubyNext.current_ruby_version) < Gem::Version.new("2.6")
13
13
  RubyNext::Core.patch refineable: Array, name: "ArrayRespondToDeconstruct", method: :deconstruct, version: "2.7" do
14
14
  <<-RUBY
15
15
  def respond_to?(mid, *)
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ RubyNext::Core.patch Array, method: :intersect?, version: "3.1" do
4
+ <<-RUBY
5
+ def intersect?(other)
6
+ !(self & other).empty?
7
+ end
8
+ RUBY
9
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ RubyNext::Core.patch Object,
4
+ name: "FrozenError",
5
+ method: nil,
6
+ refineable: [],
7
+ version: "2.5",
8
+ # avoid defining the constant twice, 'causae it's already included in core
9
+ # we only use the contents in `ruby-next core_ext`.
10
+ supported: true,
11
+ location: [__FILE__, __LINE__ + 2] do
12
+ <<-RUBY
13
+ FrozenError ||= RuntimeError
14
+ RUBY
15
+ end
@@ -11,7 +11,7 @@ RubyNext::Core.patch Object,
11
11
  supported: true,
12
12
  location: [__FILE__, __LINE__ + 2] do
13
13
  <<-RUBY
14
- class NoMatchingPatternError < RuntimeError
14
+ class NoMatchingPatternError < StandardError
15
15
  end
16
16
  RUBY
17
17
  end
@@ -1,12 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Refine Array seprately, 'cause refining modules is vulnerable to prepend:
4
- # - https://bugs.ruby-lang.org/issues/13446
5
- RubyNext::Core.patch Enumerable, method: :tally, version: "2.7", refineable: [Enumerable, Array] do
6
- <<-RUBY
7
- def tally
8
- each_with_object({}) do |v, acc|
9
- acc[v] ||= 0
3
+ # Ruby 3.1 adds an ability to pass a hash as accumulator.
4
+ #
5
+ # NOTE: We have separate patches for MRI 3.0+ and others due to the unsupported refinements vs modules behaviour.
6
+ if Enumerable.method_defined?(:tally) && ([].method(:tally).arity == 0) && !(defined?(JRUBY_VERSION) || defined?(TruffleRuby))
7
+ RubyNext::Core.patch name: "TallyWithHash", supported: false, native: nil, method: :tally, version: "3.1", refineable: [Enumerable] do
8
+ <<-'RUBY'
9
+ def tally(*attrs)
10
+ return super() if attrs.size.zero?
11
+
12
+ raise ArgumentError, "wrong number of arguments (given #{attrs.size}, expected 0..1)" if attrs.size > 1
13
+
14
+ hash = attrs.size.zero? ? {} : attrs[0].to_hash
15
+ raise FrozenError, "can't modify frozen #{hash.class}: #{hash}" if hash.frozen?
16
+
17
+ each_with_object(hash) do |v, acc|
18
+ acc[v] = 0 unless acc.key?(v)
19
+ acc[v] += 1
20
+ end
21
+ end
22
+ RUBY
23
+ end
24
+ else
25
+ RubyNext::Core.patch Enumerable, method: :tally, version: "3.1", refineable: [Enumerable, Array] do
26
+ <<-'RUBY'
27
+ def tally(acc = {})
28
+ hash = acc.to_hash
29
+ raise FrozenError, "can't modify frozen #{hash.class}: #{hash}" if hash.frozen?
30
+
31
+ each_with_object(hash) do |v, acc|
32
+ acc[v] = 0 unless acc.key?(v)
33
+ acc[v] += 1
34
+ end
35
+ end
36
+ RUBY
37
+ end
38
+ end
39
+
40
+ # This patch is intended for core extensions only (since we can not use prepend here)
41
+ RubyNext::Core.patch Enumerable, name: "TallyWithHashCoreExt", version: "3.1", supported: Enumerable.method_defined?(:tally) && ([].method(:tally).arity != 0), method: :tally, refineable: [] do
42
+ <<-'RUBY'
43
+ def tally(acc = {})
44
+ hash = acc.to_hash
45
+ raise FrozenError, "can't modify frozen #{hash.class}: #{hash}" if hash.frozen?
46
+
47
+ each_with_object(hash) do |v, acc|
48
+ acc[v] = 0 unless acc.key?(v)
10
49
  acc[v] += 1
11
50
  end
12
51
  end
@@ -9,7 +9,7 @@ end
9
9
  end
10
10
 
11
11
  # We need to hack `respond_to?` in Ruby 2.5, since it's not working with refinements
12
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.6")
12
+ if Gem::Version.new(::RubyNext.current_ruby_version) < Gem::Version.new("2.6")
13
13
  RubyNext::Core.patch refineable: Hash, name: "HashRespondToDeconstructKeys", method: :deconstruct_keys, version: "2.7" do
14
14
  <<-RUBY
15
15
  def respond_to?(mid, *)
@@ -10,9 +10,9 @@ def deconstruct_keys(keys)
10
10
 
11
11
  keys.each_with_object({}) do |k, acc|
12
12
  # if k is Symbol and not a member of a Struct return {}
13
- next if (Symbol === k || String === k) && !members.include?(k.to_sym)
13
+ return {} if (Symbol === k || String === k) && !members.include?(k.to_sym)
14
14
  # if k is Integer check that index is not ouf of bounds
15
- next if Integer === k && k > size - 1
15
+ return {} if Integer === k && k > size - 1
16
16
  acc[k] = self[k]
17
17
  end
18
18
  end
@@ -20,7 +20,7 @@ end
20
20
  end
21
21
 
22
22
  # We need to hack `respond_to?` in Ruby 2.5, since it's not working with refinements
23
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.6")
23
+ if Gem::Version.new(::RubyNext.current_ruby_version) < Gem::Version.new("2.6")
24
24
  RubyNext::Core.patch refineable: Struct, name: "StructRespondToDeconstruct", method: :deconstruct_keys, version: "2.7" do
25
25
  <<-RUBY
26
26
  def respond_to?(mid, *)
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "set"
4
4
 
5
+ require "ruby-next/config"
5
6
  require "ruby-next/utils"
6
7
 
7
8
  module RubyNext
@@ -17,7 +18,7 @@ module RubyNext
17
18
  # `core_ext` defines the strategy for core extensions:
18
19
  # - :patch — extend class directly
19
20
  # - :prepend — extend class by prepending a module (e.g., when needs `super`)
20
- def initialize(mod = nil, method:, name: nil, version:, supported: nil, native: nil, location: nil, refineable: mod, core_ext: :patch, singleton: nil)
21
+ def initialize(mod = nil, method:, version:, name: nil, supported: nil, native: nil, location: nil, refineable: mod, core_ext: :patch, singleton: nil)
21
22
  @mod = mod
22
23
  @method_name = method
23
24
  @version = version
@@ -122,8 +123,8 @@ module RubyNext
122
123
  strategy == :backports
123
124
  end
124
125
 
125
- def patch(*args, **kwargs, &block)
126
- patches << Patch.new(*args, **kwargs, &block)
126
+ def patch(...)
127
+ patches << Patch.new(...)
127
128
  end
128
129
 
129
130
  # Inject `using RubyNext` at the top of the source code
@@ -175,11 +176,12 @@ require "ruby-next/core/time/ceil"
175
176
  # Core extensions required for pattern matching
176
177
  # Required for pattern matching with refinements
177
178
  unless defined?(NoMatchingPatternError)
178
- class NoMatchingPatternError < RuntimeError
179
+ class NoMatchingPatternError < StandardError
179
180
  end
180
181
  end
181
182
 
182
183
  require "ruby-next/core/constants/no_matching_pattern_error"
184
+ require "ruby-next/core/constants/frozen_error"
183
185
  require "ruby-next/core/array/deconstruct"
184
186
  require "ruby-next/core/hash/deconstruct_keys"
185
187
  require "ruby-next/core/struct/deconstruct"
@@ -187,6 +189,8 @@ require "ruby-next/core/struct/deconstruct_keys"
187
189
 
188
190
  require "ruby-next/core/hash/except"
189
191
 
192
+ require "ruby-next/core/array/intersect"
193
+
190
194
  # Generate refinements
191
195
  RubyNext.module_eval do
192
196
  RubyNext::Core.patches.refined.each do |mod, patches|
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "ruby-next/config"
4
+ require "ruby-next/setup_self"
3
5
  require "ruby-next/core"
4
6
 
5
7
  # Monkey-patch core classes using the same patches as for refinements
@@ -8,7 +10,7 @@ RubyNext::Core.patches.extensions.each do |mod, patches|
8
10
  next if patch.supported?
9
11
 
10
12
  if patch.prepend?
11
- mod.prepend(patch.to_module)
13
+ mod.send(:prepend, patch.to_module)
12
14
  else
13
15
  mod.module_eval(patch.body, *patch.location)
14
16
  end
@@ -13,7 +13,7 @@ load_iseq = RubyVM::InstructionSequence.method(:load_iseq)
13
13
  if load_iseq.source_location[0].include?("/bootsnap/")
14
14
  Bootsnap::CompileCache::ISeq.singleton_class.prepend(
15
15
  Module.new do
16
- def input_to_storage(source, path)
16
+ def input_to_storage(source, path, *)
17
17
  return super unless RubyNext::Language.transformable?(path)
18
18
  source = RubyNext::Language.transform(source, rewriters: RubyNext::Language.current_rewriters)
19
19
 
@@ -1,9 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Load edge Ruby features
4
-
5
- require "ruby-next/language/rewriters/endless_method"
6
- RubyNext::Language.rewriters << RubyNext::Language::Rewriters::EndlessMethod
7
-
8
- require "ruby-next/language/rewriters/right_hand_assignment"
9
- RubyNext::Language.rewriters << RubyNext::Language::Rewriters::RightHandAssignment