ruby-next-core 0.8.0 → 0.10.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 +47 -0
- data/README.md +85 -11
- data/bin/transform +9 -1
- data/lib/.rbnext/2.3/ruby-next/commands/core_ext.rb +167 -0
- data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +198 -0
- data/lib/.rbnext/2.3/ruby-next/language/eval.rb +66 -0
- data/lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb +121 -0
- data/lib/.rbnext/2.3/ruby-next/language/rewriters/endless_range.rb +63 -0
- data/lib/.rbnext/2.3/ruby-next/language/rewriters/pattern_matching.rb +944 -0
- data/lib/.rbnext/2.3/ruby-next/utils.rb +65 -0
- data/lib/ruby-next.rb +8 -5
- data/lib/ruby-next/cli.rb +2 -2
- data/lib/ruby-next/commands/core_ext.rb +2 -2
- data/lib/ruby-next/commands/nextify.rb +64 -22
- data/lib/ruby-next/core.rb +39 -22
- data/lib/ruby-next/core/array/deconstruct.rb +9 -9
- data/lib/ruby-next/core/array/difference_union_intersection.rb +12 -12
- data/lib/ruby-next/core/constants/no_matching_pattern_error.rb +3 -3
- data/lib/ruby-next/core/enumerable/filter.rb +8 -8
- data/lib/ruby-next/core/enumerable/filter_map.rb +25 -25
- data/lib/ruby-next/core/enumerable/tally.rb +7 -7
- data/lib/ruby-next/core/enumerator/produce.rb +12 -12
- data/lib/ruby-next/core/hash/deconstruct_keys.rb +9 -9
- data/lib/ruby-next/core/hash/except.rb +11 -0
- data/lib/ruby-next/core/hash/merge.rb +8 -8
- data/lib/ruby-next/core/kernel/then.rb +2 -2
- data/lib/ruby-next/core/proc/compose.rb +11 -11
- data/lib/ruby-next/core/string/split.rb +6 -6
- data/lib/ruby-next/core/struct/deconstruct.rb +2 -2
- data/lib/ruby-next/core/struct/deconstruct_keys.rb +17 -17
- data/lib/ruby-next/core/symbol/end_with.rb +4 -4
- data/lib/ruby-next/core/symbol/start_with.rb +4 -4
- data/lib/ruby-next/core/time/ceil.rb +6 -5
- data/lib/ruby-next/core/time/floor.rb +4 -4
- data/lib/ruby-next/core/unboundmethod/bind_call.rb +4 -4
- data/lib/ruby-next/core_ext.rb +2 -2
- data/lib/ruby-next/language.rb +31 -5
- data/lib/ruby-next/language/eval.rb +10 -8
- data/lib/ruby-next/language/proposed.rb +3 -0
- data/lib/ruby-next/language/rewriters/args_forward.rb +24 -20
- data/lib/ruby-next/language/rewriters/base.rb +2 -2
- data/lib/ruby-next/language/rewriters/endless_method.rb +26 -3
- data/lib/ruby-next/language/rewriters/endless_range.rb +1 -0
- data/lib/ruby-next/language/rewriters/find_pattern.rb +44 -0
- data/lib/ruby-next/language/rewriters/method_reference.rb +2 -1
- data/lib/ruby-next/language/rewriters/numbered_params.rb +1 -0
- data/lib/ruby-next/language/rewriters/pattern_matching.rb +105 -13
- data/lib/ruby-next/language/rewriters/right_hand_assignment.rb +2 -1
- data/lib/ruby-next/language/rewriters/runtime.rb +6 -0
- data/lib/ruby-next/language/rewriters/runtime/dir.rb +32 -0
- data/lib/ruby-next/language/rewriters/safe_navigation.rb +87 -0
- data/lib/ruby-next/language/rewriters/shorthand_hash.rb +47 -0
- data/lib/ruby-next/language/rewriters/squiggly_heredoc.rb +36 -0
- data/lib/ruby-next/language/runtime.rb +3 -2
- data/lib/ruby-next/logging.rb +1 -1
- data/lib/ruby-next/rubocop.rb +15 -9
- data/lib/ruby-next/setup_self.rb +22 -0
- data/lib/ruby-next/utils.rb +30 -0
- data/lib/ruby-next/version.rb +1 -1
- data/lib/uby-next.rb +8 -4
- metadata +22 -7
@@ -3,14 +3,16 @@
|
|
3
3
|
module RubyNext
|
4
4
|
module Language
|
5
5
|
module KernelEval
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
if Utils.refine_modules?
|
7
|
+
refine Kernel do
|
8
|
+
def eval(source, bind = nil, *args)
|
9
|
+
new_source = ::RubyNext::Language::Runtime.transform(
|
10
|
+
source,
|
11
|
+
using: bind&.receiver == TOPLEVEL_BINDING.receiver || bind&.receiver&.is_a?(Module)
|
12
|
+
)
|
13
|
+
RubyNext.debug_source(new_source, "(#{caller_locations(1, 1).first})")
|
14
|
+
super new_source, bind, *args
|
15
|
+
end
|
14
16
|
end
|
15
17
|
end
|
16
18
|
end
|
@@ -4,3 +4,6 @@
|
|
4
4
|
|
5
5
|
require "ruby-next/language/rewriters/method_reference"
|
6
6
|
RubyNext::Language.rewriters << RubyNext::Language::Rewriters::MethodReference
|
7
|
+
|
8
|
+
require "ruby-next/language/rewriters/shorthand_hash"
|
9
|
+
RubyNext::Language.rewriters << RubyNext::Language::Rewriters::ShorthandHash
|
@@ -4,16 +4,19 @@ module RubyNext
|
|
4
4
|
module Language
|
5
5
|
module Rewriters
|
6
6
|
class ArgsForward < Base
|
7
|
-
|
8
|
-
|
7
|
+
NAME = "args-forward"
|
8
|
+
SYNTAX_PROBE = "obj = Object.new; def obj.foo(...) super(1, ...); end"
|
9
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.2")
|
9
10
|
|
10
11
|
REST = :__rest__
|
11
12
|
BLOCK = :__block__
|
12
13
|
|
13
|
-
def
|
14
|
+
def on_forward_arg(node)
|
14
15
|
context.track! self
|
15
16
|
|
16
|
-
|
17
|
+
node = super(node)
|
18
|
+
|
19
|
+
replace(node.loc.expression, "*#{REST}, &#{BLOCK}")
|
17
20
|
|
18
21
|
node.updated(
|
19
22
|
:args,
|
@@ -25,34 +28,35 @@ module RubyNext
|
|
25
28
|
end
|
26
29
|
|
27
30
|
def on_send(node)
|
28
|
-
|
31
|
+
fargs = node.children.find { |child| child.is_a?(::Parser::AST::Node) && child.type == :forwarded_args }
|
32
|
+
return super(node) unless fargs
|
33
|
+
|
34
|
+
process_fargs(node, fargs)
|
35
|
+
end
|
36
|
+
|
37
|
+
def on_super(node)
|
38
|
+
fargs = node.children.find { |child| child.is_a?(::Parser::AST::Node) && child.type == :forwarded_args }
|
39
|
+
return super(node) unless fargs
|
40
|
+
|
41
|
+
process_fargs(node, fargs)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
29
45
|
|
30
|
-
|
46
|
+
def process_fargs(node, fargs)
|
47
|
+
replace(fargs.loc.expression, "*#{REST}, &#{BLOCK}")
|
31
48
|
|
32
49
|
process(
|
33
50
|
node.updated(
|
34
51
|
nil,
|
35
52
|
[
|
36
|
-
*node.children
|
53
|
+
*node.children.take(node.children.index(fargs)),
|
37
54
|
*forwarded_args
|
38
55
|
]
|
39
56
|
)
|
40
57
|
)
|
41
58
|
end
|
42
59
|
|
43
|
-
def on_super(node)
|
44
|
-
return super(node) unless node.children[0]&.type == :forwarded_args
|
45
|
-
|
46
|
-
replace(node.children[0].loc.expression, "*#{REST}, &#{BLOCK}")
|
47
|
-
|
48
|
-
node.updated(
|
49
|
-
nil,
|
50
|
-
forwarded_args
|
51
|
-
)
|
52
|
-
end
|
53
|
-
|
54
|
-
private
|
55
|
-
|
56
60
|
def forwarded_args
|
57
61
|
[
|
58
62
|
s(:splat, s(:lvar, REST)),
|
@@ -34,7 +34,7 @@ module RubyNext
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def key?(name)
|
37
|
-
!!fetch(name) { false }
|
37
|
+
!!fetch(name) { false } # rubocop:disable Style/RedundantFetchBlock
|
38
38
|
end
|
39
39
|
|
40
40
|
def fetch(name)
|
@@ -59,7 +59,7 @@ module RubyNext
|
|
59
59
|
def unsupported_syntax?
|
60
60
|
save_verbose, $VERBOSE = $VERBOSE, nil
|
61
61
|
eval_mid = Kernel.respond_to?(:eval_without_ruby_next) ? :eval_without_ruby_next : :eval
|
62
|
-
Kernel.send eval_mid, self::SYNTAX_PROBE
|
62
|
+
Kernel.send eval_mid, self::SYNTAX_PROBE, nil, __FILE__, __LINE__
|
63
63
|
false
|
64
64
|
rescue SyntaxError, NameError
|
65
65
|
true
|
@@ -4,8 +4,16 @@ module RubyNext
|
|
4
4
|
module Language
|
5
5
|
module Rewriters
|
6
6
|
class EndlessMethod < Base
|
7
|
+
NAME = "endless-method"
|
7
8
|
SYNTAX_PROBE = "obj = Object.new; def obj.foo() = 42"
|
8
|
-
MIN_SUPPORTED_VERSION = Gem::Version.new("
|
9
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("3.0.0")
|
10
|
+
|
11
|
+
unless Parser::Meta::NODE_TYPES.include?(:def_e)
|
12
|
+
def on_def(node)
|
13
|
+
return on_def_e(node) if node.loc.end.nil?
|
14
|
+
super(node)
|
15
|
+
end
|
16
|
+
end
|
9
17
|
|
10
18
|
def on_def_e(node)
|
11
19
|
context.track! self
|
@@ -13,24 +21,39 @@ module RubyNext
|
|
13
21
|
replace(node.loc.assignment, "; ")
|
14
22
|
insert_after(node.loc.expression, "; end")
|
15
23
|
|
24
|
+
new_loc = node.loc.dup
|
25
|
+
new_loc.instance_variable_set(:@end, node.loc.expression)
|
26
|
+
|
16
27
|
process(
|
17
28
|
node.updated(
|
18
29
|
:def,
|
19
|
-
node.children
|
30
|
+
node.children,
|
31
|
+
location: new_loc
|
20
32
|
)
|
21
33
|
)
|
22
34
|
end
|
23
35
|
|
36
|
+
unless Parser::Meta::NODE_TYPES.include?(:def_e)
|
37
|
+
def on_defs(node)
|
38
|
+
return on_defs_e(node) if node.loc.end.nil?
|
39
|
+
super(node)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
24
43
|
def on_defs_e(node)
|
25
44
|
context.track! self
|
26
45
|
|
27
46
|
replace(node.loc.assignment, "; ")
|
28
47
|
insert_after(node.loc.expression, "; end")
|
29
48
|
|
49
|
+
new_loc = node.loc.dup
|
50
|
+
new_loc.instance_variable_set(:@end, node.loc.expression)
|
51
|
+
|
30
52
|
process(
|
31
53
|
node.updated(
|
32
54
|
:defs,
|
33
|
-
node.children
|
55
|
+
node.children,
|
56
|
+
location: new_loc
|
34
57
|
)
|
35
58
|
)
|
36
59
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ruby-next/language/rewriters/pattern_matching"
|
4
|
+
|
5
|
+
module RubyNext
|
6
|
+
module Language
|
7
|
+
module Rewriters
|
8
|
+
using RubyNext
|
9
|
+
|
10
|
+
# Separate pattern matching rewriter for Ruby 2.7 to
|
11
|
+
# transpile only case...in with a find pattern
|
12
|
+
class FindPattern < PatternMatching
|
13
|
+
NAME = "pattern-matching-find-pattern"
|
14
|
+
SYNTAX_PROBE = "case 0; in [*,0,*]; true; else; 1; end"
|
15
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("3.0.0")
|
16
|
+
|
17
|
+
def on_case_match(node)
|
18
|
+
@has_find_pattern = false
|
19
|
+
process_regular_node(node).then do |new_node|
|
20
|
+
return new_node unless has_find_pattern
|
21
|
+
super(node)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def on_in_match(node)
|
26
|
+
@has_find_pattern = false
|
27
|
+
process_regular_node(node).then do |new_node|
|
28
|
+
return new_node unless has_find_pattern
|
29
|
+
super(node)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def on_find_pattern(node)
|
34
|
+
@has_find_pattern = true
|
35
|
+
super(node)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
attr_reader :has_find_pattern
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -4,8 +4,9 @@ module RubyNext
|
|
4
4
|
module Language
|
5
5
|
module Rewriters
|
6
6
|
class MethodReference < Base
|
7
|
+
NAME = "method-reference"
|
7
8
|
SYNTAX_PROBE = "Language.:transform"
|
8
|
-
MIN_SUPPORTED_VERSION = Gem::Version.new("
|
9
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("3.0.0")
|
9
10
|
|
10
11
|
def on_meth_ref(node)
|
11
12
|
context.track! self
|
@@ -10,6 +10,16 @@ module RubyNext
|
|
10
10
|
def to_ast_node
|
11
11
|
self
|
12
12
|
end
|
13
|
+
|
14
|
+
# Useful to generate simple operation nodes
|
15
|
+
# (e.g., 'a + b')
|
16
|
+
def -(val)
|
17
|
+
::Parser::AST::Node.new(:send, [self, :-, val.to_ast_node])
|
18
|
+
end
|
19
|
+
|
20
|
+
def +(val)
|
21
|
+
::Parser::AST::Node.new(:send, [self, :+, val.to_ast_node])
|
22
|
+
end
|
13
23
|
end
|
14
24
|
|
15
25
|
refine String do
|
@@ -216,6 +226,7 @@ module RubyNext
|
|
216
226
|
end
|
217
227
|
|
218
228
|
class PatternMatching < Base
|
229
|
+
NAME = "pattern-matching"
|
219
230
|
SYNTAX_PROBE = "case 0; in 0; true; else; 1; end"
|
220
231
|
MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
|
221
232
|
|
@@ -248,7 +259,7 @@ module RubyNext
|
|
248
259
|
rewrite_case_in! node, matchee_ast, case_clause
|
249
260
|
|
250
261
|
node.updated(
|
251
|
-
:
|
262
|
+
:kwbegin,
|
252
263
|
[
|
253
264
|
matchee_ast, case_clause
|
254
265
|
]
|
@@ -287,7 +298,7 @@ module RubyNext
|
|
287
298
|
pattern
|
288
299
|
]
|
289
300
|
).tap do |new_node|
|
290
|
-
replace(node.loc.expression, new_node)
|
301
|
+
replace(node.loc.expression, inline_blocks(unparse(new_node)))
|
291
302
|
end
|
292
303
|
end
|
293
304
|
|
@@ -306,14 +317,14 @@ module RubyNext
|
|
306
317
|
body_indent = " " * clause.children[2].loc.column
|
307
318
|
replace(
|
308
319
|
clause.loc.expression,
|
309
|
-
"when #{unparse(new_node.children[i].children[0])}" \
|
320
|
+
"when #{inline_blocks(unparse(new_node.children[i].children[0]))}" \
|
310
321
|
"#{padding}" \
|
311
322
|
"#{body_indent}#{clause.children[2].loc.expression.source}"
|
312
323
|
)
|
313
324
|
else
|
314
325
|
replace(
|
315
326
|
clause.loc.keyword.end.join(clause.children[0].loc.expression.end),
|
316
|
-
new_node.children[i].children[0]
|
327
|
+
inline_blocks(unparse(new_node.children[i].children[0]))
|
317
328
|
)
|
318
329
|
remove(clause.children[1].loc.expression) if clause.children[1]
|
319
330
|
replace(clause.loc.keyword, "when ")
|
@@ -429,6 +440,8 @@ module RubyNext
|
|
429
440
|
right =
|
430
441
|
if node.children.empty?
|
431
442
|
case_eq_clause(s(:array), s(:lvar, locals[:arr]))
|
443
|
+
elsif node.children.size > 1 && node.children.first.type == :match_rest && node.children.last.type == :match_rest
|
444
|
+
array_find(*node.children)
|
432
445
|
else
|
433
446
|
array_element(0, *node.children)
|
434
447
|
end
|
@@ -442,6 +455,7 @@ module RubyNext
|
|
442
455
|
end
|
443
456
|
|
444
457
|
alias array_pattern_with_tail_clause array_pattern_clause
|
458
|
+
alias find_pattern_clause array_pattern_clause
|
445
459
|
|
446
460
|
def deconstruct_node(matchee)
|
447
461
|
context.use_ruby_next!
|
@@ -477,9 +491,80 @@ module RubyNext
|
|
477
491
|
end
|
478
492
|
end
|
479
493
|
|
494
|
+
# [*a, 1, 2, *] -> arr.find.with_index { |_, i| (a = arr.take(i)) && arr[i] == 1 && arr[i + 1] == 2 }
|
495
|
+
def array_find(head, *nodes, tail)
|
496
|
+
index = s(:lvar, :__i__)
|
497
|
+
|
498
|
+
match_vars = []
|
499
|
+
|
500
|
+
head_match =
|
501
|
+
unless head.children.empty?
|
502
|
+
match_vars << s(:lvasgn, head.children[0].children[0])
|
503
|
+
|
504
|
+
arr_take = s(:send,
|
505
|
+
s(:lvar, locals[:arr]),
|
506
|
+
:take,
|
507
|
+
index)
|
508
|
+
|
509
|
+
match_var_clause(head.children[0], arr_take)
|
510
|
+
end
|
511
|
+
|
512
|
+
tail_match =
|
513
|
+
unless tail.children.empty?
|
514
|
+
match_vars << s(:lvasgn, tail.children[0].children[0])
|
515
|
+
|
516
|
+
match_var_clause(tail.children[0], arr_slice(index + nodes.size, -1))
|
517
|
+
end
|
518
|
+
|
519
|
+
nodes.each do |node|
|
520
|
+
if node.type == :match_var
|
521
|
+
match_vars << s(:lvasgn, node.children[0])
|
522
|
+
elsif node.type == :match_as
|
523
|
+
match_vars << s(:lvasgn, node.children[1].children[0])
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
pattern = array_rest_element(*nodes, index).then do |needle|
|
528
|
+
next needle unless head_match
|
529
|
+
s(:and,
|
530
|
+
needle,
|
531
|
+
head_match)
|
532
|
+
end.then do |headed_needle|
|
533
|
+
next headed_needle unless tail_match
|
534
|
+
|
535
|
+
s(:and,
|
536
|
+
headed_needle,
|
537
|
+
tail_match)
|
538
|
+
end
|
539
|
+
|
540
|
+
s(:block,
|
541
|
+
s(:send,
|
542
|
+
s(:send,
|
543
|
+
s(:lvar, locals[:arr]),
|
544
|
+
:find),
|
545
|
+
:with_index),
|
546
|
+
s(:args,
|
547
|
+
s(:arg, :_),
|
548
|
+
s(:arg, :__i__)),
|
549
|
+
pattern).then do |block|
|
550
|
+
next block if match_vars.empty?
|
551
|
+
|
552
|
+
# We need to declare match vars outside of `find` block
|
553
|
+
locals_declare = s(:masgn,
|
554
|
+
s(:mlhs, *match_vars),
|
555
|
+
s(:nil))
|
556
|
+
|
557
|
+
s(:or,
|
558
|
+
locals_declare,
|
559
|
+
block)
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
480
563
|
def array_match_rest(index, node, *tail)
|
564
|
+
size = tail.size + 1
|
481
565
|
child = node.children[0]
|
482
|
-
|
566
|
+
|
567
|
+
rest = arr_slice(index, -size).then do |r|
|
483
568
|
next r unless child
|
484
569
|
|
485
570
|
match_var_clause(
|
@@ -492,16 +577,16 @@ module RubyNext
|
|
492
577
|
|
493
578
|
s(:and,
|
494
579
|
rest,
|
495
|
-
array_rest_element(*tail))
|
580
|
+
array_rest_element(*tail, -(size - 1)))
|
496
581
|
end
|
497
582
|
|
498
|
-
def array_rest_element(head, *tail)
|
499
|
-
send("#{head.type}_array_element", head,
|
583
|
+
def array_rest_element(head, *tail, index)
|
584
|
+
send("#{head.type}_array_element", head, index).then do |node|
|
500
585
|
next node if tail.empty?
|
501
586
|
|
502
587
|
s(:and,
|
503
588
|
node,
|
504
|
-
array_rest_element(*tail))
|
589
|
+
array_rest_element(*tail, index + 1))
|
505
590
|
end
|
506
591
|
end
|
507
592
|
|
@@ -548,12 +633,12 @@ module RubyNext
|
|
548
633
|
s(:index, arr, index.to_ast_node)
|
549
634
|
end
|
550
635
|
|
551
|
-
def
|
636
|
+
def arr_slice(lindex, rindex, arr = s(:lvar, locals[:arr]))
|
552
637
|
s(:index,
|
553
638
|
arr,
|
554
639
|
s(:irange,
|
555
|
-
|
556
|
-
|
640
|
+
lindex.to_ast_node,
|
641
|
+
rindex.to_ast_node))
|
557
642
|
end
|
558
643
|
|
559
644
|
#=========== ARRAY PATTERN (END) ===============
|
@@ -816,11 +901,12 @@ module RubyNext
|
|
816
901
|
end
|
817
902
|
|
818
903
|
def respond_to_missing?(mid, *)
|
819
|
-
return true if mid.match?(/_(clause|array_element)/)
|
904
|
+
return true if mid.to_s.match?(/_(clause|array_element)/)
|
820
905
|
super
|
821
906
|
end
|
822
907
|
|
823
908
|
def method_missing(mid, *args, &block)
|
909
|
+
mid = mid.to_s
|
824
910
|
return case_eq_clause(*args) if mid.match?(/_clause$/)
|
825
911
|
return case_eq_array_element(*args) if mid.match?(/_array_element$/)
|
826
912
|
return case_eq_hash_element(*args) if mid.match?(/_hash_element$/)
|
@@ -846,6 +932,12 @@ module RubyNext
|
|
846
932
|
|
847
933
|
deconstructed_keys[key] = :"k#{deconstructed_keys.size}"
|
848
934
|
end
|
935
|
+
|
936
|
+
# Unparser generates `do .. end` blocks, we want to
|
937
|
+
# have single-line blocks with `{ ... }`.
|
938
|
+
def inline_blocks(source)
|
939
|
+
source.gsub(/do \|_, __i__\|\n\s*([^\n]+)\n\s*end/, '{ |_, __i__| \1 }')
|
940
|
+
end
|
849
941
|
end
|
850
942
|
end
|
851
943
|
end
|