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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +41 -0
- data/README.md +25 -16
- data/lib/.rbnext/2.1/ruby-next/core.rb +206 -0
- data/lib/.rbnext/2.1/ruby-next/language.rb +227 -0
- data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +2 -2
- data/lib/.rbnext/2.3/ruby-next/language/eval.rb +4 -4
- data/lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb +23 -5
- data/lib/.rbnext/2.3/ruby-next/language/rewriters/endless_range.rb +1 -1
- data/lib/.rbnext/2.3/ruby-next/language/rewriters/pattern_matching.rb +20 -17
- data/lib/.rbnext/2.3/ruby-next/utils.rb +1 -1
- data/lib/.rbnext/2.7/ruby-next/core.rb +206 -0
- data/lib/ruby-next/commands/nextify.rb +1 -1
- data/lib/ruby-next/config.rb +50 -0
- data/lib/ruby-next/core/array/deconstruct.rb +1 -1
- data/lib/ruby-next/core/array/intersect.rb +9 -0
- data/lib/ruby-next/core/constants/frozen_error.rb +15 -0
- data/lib/ruby-next/core/constants/no_matching_pattern_error.rb +1 -1
- data/lib/ruby-next/core/enumerable/tally.rb +46 -7
- data/lib/ruby-next/core/hash/deconstruct_keys.rb +1 -1
- data/lib/ruby-next/core/struct/deconstruct_keys.rb +3 -3
- data/lib/ruby-next/core.rb +8 -4
- data/lib/ruby-next/core_ext.rb +3 -1
- data/lib/ruby-next/language/bootsnap.rb +1 -1
- data/lib/ruby-next/language/edge.rb +0 -6
- data/lib/ruby-next/language/eval.rb +3 -3
- data/lib/ruby-next/language/parser.rb +16 -0
- data/lib/ruby-next/language/rewriters/args_forward.rb +14 -6
- data/lib/ruby-next/language/rewriters/args_forward_leading.rb +75 -0
- data/lib/ruby-next/language/rewriters/base.rb +19 -1
- data/lib/ruby-next/language/rewriters/in_pattern.rb +56 -0
- data/lib/ruby-next/language/rewriters/method_reference.rb +1 -1
- data/lib/ruby-next/language/rewriters/numeric_literals.rb +41 -0
- data/lib/ruby-next/language/rewriters/pattern_matching.rb +18 -15
- data/lib/ruby-next/language/rewriters/required_kwargs.rb +39 -0
- data/lib/ruby-next/language/rewriters/safe_navigation.rb +42 -29
- data/lib/ruby-next/language/rewriters/shorthand_hash.rb +1 -1
- data/lib/ruby-next/language/setup.rb +7 -4
- data/lib/ruby-next/language/unparser.rb +5 -0
- data/lib/ruby-next/language.rb +62 -44
- data/lib/ruby-next/rubocop.rb +9 -18
- data/lib/ruby-next/setup_self.rb +5 -3
- data/lib/ruby-next/version.rb +1 -1
- data/lib/ruby-next.rb +5 -38
- metadata +20 -18
- data/lib/.rbnext/2.3/ruby-next/language/rewriters/right_hand_assignment.rb +0 -117
- 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,
|
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
|
-
((
|
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
|
-
((
|
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
|
-
((
|
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
|
-
((
|
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
|
-
((
|
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 -(
|
17
|
-
::Parser::AST::Node.new(:send, [self, :-,
|
16
|
+
def -(other)
|
17
|
+
::Parser::AST::Node.new(:send, [self, :-, other.to_ast_node])
|
18
18
|
end
|
19
19
|
|
20
|
-
def +(
|
21
|
-
::Parser::AST::Node.new(:send, [self, :+,
|
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
|
222
|
-
|
223
|
-
|
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
|
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 ((!
|
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 ((!
|
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
|
-
|
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
|
-
|
887
|
-
|
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
|
-
|
893
|
-
|
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
|
-
((
|
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
|
@@ -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(
|
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,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
|
@@ -1,12 +1,51 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
#
|
4
|
-
#
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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(
|
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
|
-
|
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
|
-
|
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(
|
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, *)
|
data/lib/ruby-next/core.rb
CHANGED
@@ -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,
|
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(
|
126
|
-
patches << Patch.new(
|
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 <
|
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|
|
data/lib/ruby-next/core_ext.rb
CHANGED
@@ -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
|
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
|