ruby-next-core 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -0
- data/README.md +69 -7
- data/bin/ruby-next +1 -1
- data/lib/ruby-next/cli.rb +45 -6
- data/lib/ruby-next/commands/core_ext.rb +166 -0
- data/lib/ruby-next/commands/nextify.rb +18 -3
- data/lib/ruby-next/core.rb +149 -1
- data/lib/ruby-next/core/array/deconstruct.rb +21 -0
- data/lib/ruby-next/core/array/difference_union_intersection.rb +15 -21
- data/lib/ruby-next/core/constants/no_matching_pattern_error.rb +17 -0
- data/lib/ruby-next/core/enumerable/filter.rb +20 -18
- data/lib/ruby-next/core/enumerable/filter_map.rb +28 -40
- data/lib/ruby-next/core/enumerable/tally.rb +9 -23
- data/lib/ruby-next/core/enumerator/produce.rb +12 -14
- data/lib/ruby-next/core/hash/deconstruct_keys.rb +21 -0
- data/lib/ruby-next/core/hash/merge.rb +8 -10
- data/lib/ruby-next/core/kernel/then.rb +7 -9
- data/lib/ruby-next/core/proc/compose.rb +12 -14
- data/lib/ruby-next/core/string/split.rb +11 -0
- data/lib/ruby-next/core/struct/deconstruct.rb +7 -0
- data/lib/ruby-next/core/struct/deconstruct_keys.rb +34 -0
- data/lib/ruby-next/core/time/ceil.rb +10 -0
- data/lib/ruby-next/core/time/floor.rb +9 -0
- data/lib/ruby-next/core/unboundmethod/bind_call.rb +9 -0
- data/lib/ruby-next/core_ext.rb +18 -0
- data/lib/ruby-next/language.rb +4 -2
- data/lib/ruby-next/language/parser.rb +5 -1
- data/lib/ruby-next/language/rewriters/base.rb +2 -2
- data/lib/ruby-next/language/rewriters/method_reference.rb +3 -1
- data/lib/ruby-next/language/rewriters/numbered_params.rb +2 -2
- data/lib/ruby-next/language/rewriters/pattern_matching.rb +36 -17
- data/lib/ruby-next/language/runtime.rb +2 -3
- data/lib/ruby-next/version.rb +1 -1
- metadata +13 -3
- data/lib/ruby-next/core/pattern_matching.rb +0 -37
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RubyNext::Core.patch Hash, method: :deconstruct_keys, version: "2.7" do
|
4
|
+
<<~RUBY
|
5
|
+
def deconstruct_keys(_)
|
6
|
+
self
|
7
|
+
end
|
8
|
+
RUBY
|
9
|
+
end
|
10
|
+
|
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")
|
13
|
+
RubyNext::Core.patch refineable: Hash, name: "HashRespondToDeconstructKeys", method: :deconstruct_keys, version: "2.7" do
|
14
|
+
<<~RUBY
|
15
|
+
def respond_to?(mid, *)
|
16
|
+
return true if mid == :deconstruct_keys
|
17
|
+
super
|
18
|
+
end
|
19
|
+
RUBY
|
20
|
+
end
|
21
|
+
end
|
@@ -1,16 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
return dup if others.size == 0
|
3
|
+
RubyNext::Core.patch Hash, method: :merge, version: "2.6", supported: {}.method(:merge).arity < 0, core_ext: :prepend do
|
4
|
+
<<~RUBY
|
5
|
+
def merge(*others)
|
6
|
+
return super if others.size == 1
|
7
|
+
return dup if others.size == 0
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
end
|
9
|
+
merge(others.shift).tap do |new_h|
|
10
|
+
others.each { |h| new_h.merge!(h) }
|
13
11
|
end
|
14
12
|
end
|
15
|
-
|
13
|
+
RUBY
|
16
14
|
end
|
@@ -1,12 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
end
|
11
|
-
end
|
3
|
+
# Refine object, 'cause refining modules (Kernel) is vulnerable to prepend:
|
4
|
+
# - https://bugs.ruby-lang.org/issues/13446
|
5
|
+
# - Rails added `Kernel.prepend` in 6.1: https://github.com/rails/rails/commit/3124007bd674dcdc9c3b5c6b2964dfb7a1a0733c
|
6
|
+
RubyNext::Core.patch Kernel, method: :then, version: "2.6", refineable: Object do
|
7
|
+
<<~RUBY
|
8
|
+
alias then yield_self
|
9
|
+
RUBY
|
12
10
|
end
|
@@ -1,21 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# rubocop:disable Style/LambdaCall
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
end
|
4
|
+
RubyNext::Core.patch Proc, name: "ProcCompose", method: :<<, version: "2.6" do
|
5
|
+
<<~RUBY
|
6
|
+
def <<(other)
|
7
|
+
raise TypeError, "callable object is expected" unless other.respond_to?(:call)
|
8
|
+
this = self
|
9
|
+
proc { |*args, &block| this.(other.(*args, &block)) }
|
10
|
+
end
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
end
|
12
|
+
def >>(other)
|
13
|
+
raise TypeError, "callable object is expected" unless other.respond_to?(:call)
|
14
|
+
this = self
|
15
|
+
proc { |*args, &block| other.(this.(*args, &block)) }
|
18
16
|
end
|
19
|
-
|
17
|
+
RUBY
|
20
18
|
end
|
21
19
|
# rubocop:enable Style/LambdaCall
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RubyNext::Core.patch String, method: :split, version: "2.6", supported: ("a b".split(" ", &proc {}) == "a b"), core_ext: :prepend do
|
4
|
+
<<~RUBY
|
5
|
+
def split(*args, &block)
|
6
|
+
return super unless block_given?
|
7
|
+
super.each { |el| yield el }
|
8
|
+
self
|
9
|
+
end
|
10
|
+
RUBY
|
11
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Source: https://github.com/ruby/ruby/blob/b76a21aa45fff75909a66f8b20fc5856705f7862/struct.c#L953-L980
|
4
|
+
RubyNext::Core.patch Struct, method: :deconstruct_keys, version: "2.7" do
|
5
|
+
<<~'RUBY'
|
6
|
+
def deconstruct_keys(keys)
|
7
|
+
raise TypeError, "wrong argument type #{keys.class} (expected Array or nil)" if keys && !keys.is_a?(Array)
|
8
|
+
|
9
|
+
return to_h unless keys
|
10
|
+
|
11
|
+
return {} if size < keys.size
|
12
|
+
|
13
|
+
keys.each_with_object({}) do |k, acc|
|
14
|
+
# if k is Symbol and not a member of a Struct return {}
|
15
|
+
next if (Symbol === k || String === k) && !members.include?(k.to_sym)
|
16
|
+
# if k is Integer check that index is not ouf of bounds
|
17
|
+
next if Integer === k && k > size - 1
|
18
|
+
acc[k] = self[k]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
RUBY
|
22
|
+
end
|
23
|
+
|
24
|
+
# We need to hack `respond_to?` in Ruby 2.5, since it's not working with refinements
|
25
|
+
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.6")
|
26
|
+
RubyNext::Core.patch refineable: Struct, name: "StructRespondToDeconstruct", method: :deconstruct_keys, version: "2.7" do
|
27
|
+
<<~RUBY
|
28
|
+
def respond_to?(mid, *)
|
29
|
+
return true if mid == :deconstruct_keys || mid == :deconstruct
|
30
|
+
super
|
31
|
+
end
|
32
|
+
RUBY
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "core"
|
4
|
+
|
5
|
+
# Monkey-patch core classes using the same patches as for refinements
|
6
|
+
RubyNext::Core.patches.extensions.each do |mod, patches|
|
7
|
+
patches.each do |patch|
|
8
|
+
next if patch.supported?
|
9
|
+
|
10
|
+
if patch.prepend?
|
11
|
+
mod.prepend(patch.to_module)
|
12
|
+
else
|
13
|
+
mod.module_eval(patch.body, *patch.location)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
RubyNext::Core.strategy = :core_ext
|
data/lib/ruby-next/language.rb
CHANGED
@@ -6,7 +6,6 @@ gem "unparser", ">= 0.4.7"
|
|
6
6
|
require "set"
|
7
7
|
|
8
8
|
require "ruby-next"
|
9
|
-
using RubyNext
|
10
9
|
|
11
10
|
module RubyNext
|
12
11
|
# Language module contains tools to transpile newer Ruby syntax
|
@@ -19,6 +18,8 @@ module RubyNext
|
|
19
18
|
# - Each processor may modify the AST
|
20
19
|
# - Generates a transpiled source code from the transformed AST (via the `unparser` gem)
|
21
20
|
module Language
|
21
|
+
using RubyNext
|
22
|
+
|
22
23
|
require "ruby-next/language/parser"
|
23
24
|
require "ruby-next/language/unparser"
|
24
25
|
|
@@ -62,7 +63,7 @@ module RubyNext
|
|
62
63
|
attr_accessor :rewriters
|
63
64
|
attr_reader :watch_dirs
|
64
65
|
|
65
|
-
def transform(source, rewriters: self.rewriters, using:
|
66
|
+
def transform(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new)
|
66
67
|
parse(source).then do |ast|
|
67
68
|
rewriters.inject(ast) do |tree, rewriter|
|
68
69
|
rewriter.new(context).process(tree)
|
@@ -71,6 +72,7 @@ module RubyNext
|
|
71
72
|
|
72
73
|
Unparser.unparse(new_ast)
|
73
74
|
end.then do |source|
|
75
|
+
next source unless RubyNext::Core.refine?
|
74
76
|
next source unless using && context.use_ruby_next?
|
75
77
|
|
76
78
|
Core.inject! source.dup
|
@@ -10,7 +10,11 @@ module RubyNext
|
|
10
10
|
|
11
11
|
class << self
|
12
12
|
def parser
|
13
|
-
::Parser::Ruby27.new(Builder.new)
|
13
|
+
::Parser::Ruby27.new(Builder.new).tap do |prs|
|
14
|
+
prs.diagnostics.tap do |diagnostics|
|
15
|
+
diagnostics.all_errors_are_fatal = true
|
16
|
+
end
|
17
|
+
end
|
14
18
|
end
|
15
19
|
|
16
20
|
def parse(source, file = "(string)")
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
using RubyNext
|
4
|
-
|
5
3
|
module RubyNext
|
6
4
|
module Language
|
7
5
|
module Rewriters
|
6
|
+
using RubyNext
|
7
|
+
|
8
8
|
CUSTOM_PARSER_REQUIRED = <<~MSG
|
9
9
|
The %s feature is not a part of the latest stable Ruby release
|
10
10
|
and is not supported by your Parser gem version.
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
using RubyNext
|
4
|
-
|
5
3
|
module RubyNext
|
6
4
|
module Language
|
7
5
|
module Rewriters
|
6
|
+
using RubyNext
|
7
|
+
|
8
8
|
using(Module.new do
|
9
9
|
refine ::Parser::AST::Node do
|
10
10
|
def to_ast_node
|
@@ -66,8 +66,6 @@ module RubyNext
|
|
66
66
|
def on_in_match(node)
|
67
67
|
context.track! self
|
68
68
|
|
69
|
-
@deconstructed = []
|
70
|
-
|
71
69
|
matchee =
|
72
70
|
s(:lvasgn, MATCHEE, node.children[0])
|
73
71
|
|
@@ -146,10 +144,10 @@ module RubyNext
|
|
146
144
|
s(:or, *children)
|
147
145
|
end
|
148
146
|
|
149
|
-
def match_as_clause(node)
|
147
|
+
def match_as_clause(node, right = s(:lvar, locals[:matchee]))
|
150
148
|
s(:and,
|
151
149
|
case_eq_clause(node.children[0]),
|
152
|
-
match_var_clause(node.children[1],
|
150
|
+
match_var_clause(node.children[1], right))
|
153
151
|
end
|
154
152
|
|
155
153
|
def match_var_clause(node, left = s(:lvar, locals[:matchee]))
|
@@ -195,6 +193,10 @@ module RubyNext
|
|
195
193
|
s(:and,
|
196
194
|
dnode,
|
197
195
|
right)
|
196
|
+
end.then do |right|
|
197
|
+
s(:and,
|
198
|
+
respond_to_check(matchee, :deconstruct),
|
199
|
+
right)
|
198
200
|
end
|
199
201
|
end
|
200
202
|
|
@@ -202,20 +204,21 @@ module RubyNext
|
|
202
204
|
|
203
205
|
def deconstruct_node(matchee)
|
204
206
|
# only deconstruct once per case
|
205
|
-
return if deconstructed
|
207
|
+
return if deconstructed&.include?(locals[:arr])
|
206
208
|
|
207
209
|
context.use_ruby_next!
|
208
210
|
|
209
211
|
right = s(:send, matchee, :deconstruct)
|
210
212
|
|
211
|
-
deconstructed << locals[:arr]
|
213
|
+
deconstructed << locals[:arr] if deconstructed
|
214
|
+
|
212
215
|
s(:and,
|
213
216
|
s(:or,
|
214
217
|
s(:lvasgn, locals[:arr], right),
|
215
218
|
s(:true)), # rubocop:disable Lint/BooleanSymbol
|
216
219
|
s(:or,
|
217
220
|
case_eq_clause(s(:const, nil, :Array), s(:lvar, locals[:arr])),
|
218
|
-
raise_error(:TypeError)))
|
221
|
+
raise_error(:TypeError, "#deconstruct must return Array")))
|
219
222
|
end
|
220
223
|
|
221
224
|
def array_element(index, head, *tail)
|
@@ -282,6 +285,10 @@ module RubyNext
|
|
282
285
|
match_var_clause(node, arr_item_at(index))
|
283
286
|
end
|
284
287
|
|
288
|
+
def match_as_array_element(node, index)
|
289
|
+
match_as_clause(node, arr_item_at(index))
|
290
|
+
end
|
291
|
+
|
285
292
|
def pin_array_element(node, index)
|
286
293
|
case_eq_array_element node.children[0], index
|
287
294
|
end
|
@@ -321,11 +328,15 @@ module RubyNext
|
|
321
328
|
hash_element(*node.children)
|
322
329
|
end
|
323
330
|
|
324
|
-
|
331
|
+
next dnode if right.nil?
|
325
332
|
|
326
333
|
s(:and,
|
327
334
|
dnode,
|
328
335
|
right)
|
336
|
+
end.then do |right|
|
337
|
+
s(:and,
|
338
|
+
respond_to_check(matchee, :deconstruct_keys),
|
339
|
+
right)
|
329
340
|
end
|
330
341
|
end
|
331
342
|
|
@@ -359,11 +370,12 @@ module RubyNext
|
|
359
370
|
end
|
360
371
|
|
361
372
|
# Create a copy of the original hash if already deconstructed
|
362
|
-
|
373
|
+
# FIXME: need better algorithm to handle different key sets
|
374
|
+
# return hash_dup if deconstructed&.include?(locals[:hash])
|
363
375
|
|
364
376
|
context.use_ruby_next!
|
365
377
|
|
366
|
-
deconstructed << locals[:hash]
|
378
|
+
deconstructed << locals[:hash] if deconstructed
|
367
379
|
|
368
380
|
right = s(:send,
|
369
381
|
matchee, :deconstruct_keys, keys)
|
@@ -375,7 +387,7 @@ module RubyNext
|
|
375
387
|
s(:and,
|
376
388
|
s(:or,
|
377
389
|
case_eq_clause(s(:const, nil, :Hash), s(:lvar, locals[:hash, :src])),
|
378
|
-
raise_error(:TypeError)),
|
390
|
+
raise_error(:TypeError, "#deconstruct_keys must return Hash")),
|
379
391
|
hash_dup))
|
380
392
|
end
|
381
393
|
|
@@ -491,14 +503,21 @@ module RubyNext
|
|
491
503
|
end
|
492
504
|
|
493
505
|
def no_matching_pattern
|
494
|
-
raise_error
|
506
|
+
raise_error(
|
507
|
+
:NoMatchingPatternError,
|
508
|
+
s(:send,
|
509
|
+
s(:lvar, locals[:matchee]), :inspect)
|
510
|
+
)
|
495
511
|
end
|
496
512
|
|
497
|
-
def raise_error(type)
|
513
|
+
def raise_error(type, msg = "")
|
498
514
|
s(:send, s(:const, nil, :Kernel), :raise,
|
499
515
|
s(:const, nil, type),
|
500
|
-
|
501
|
-
|
516
|
+
msg.to_ast_node)
|
517
|
+
end
|
518
|
+
|
519
|
+
def respond_to_check(node, mid)
|
520
|
+
s(:send, node, :respond_to?, mid.to_ast_node)
|
502
521
|
end
|
503
522
|
|
504
523
|
def respond_to_missing?(mid, *)
|
@@ -7,13 +7,12 @@ require "ruby-next/utils"
|
|
7
7
|
require "ruby-next/language"
|
8
8
|
require "ruby-next/language/eval"
|
9
9
|
|
10
|
-
using RubyNext
|
11
|
-
|
12
10
|
module RubyNext
|
13
11
|
module Language
|
14
12
|
# Module responsible for runtime transformations
|
15
|
-
|
16
13
|
module Runtime
|
14
|
+
using RubyNext
|
15
|
+
|
17
16
|
class << self
|
18
17
|
include Utils
|
19
18
|
|