ruby-next-core 0.10.5 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
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