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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -0
  3. data/README.md +85 -11
  4. data/bin/transform +9 -1
  5. data/lib/.rbnext/2.3/ruby-next/commands/core_ext.rb +167 -0
  6. data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +198 -0
  7. data/lib/.rbnext/2.3/ruby-next/language/eval.rb +66 -0
  8. data/lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb +121 -0
  9. data/lib/.rbnext/2.3/ruby-next/language/rewriters/endless_range.rb +63 -0
  10. data/lib/.rbnext/2.3/ruby-next/language/rewriters/pattern_matching.rb +944 -0
  11. data/lib/.rbnext/2.3/ruby-next/utils.rb +65 -0
  12. data/lib/ruby-next.rb +8 -5
  13. data/lib/ruby-next/cli.rb +2 -2
  14. data/lib/ruby-next/commands/core_ext.rb +2 -2
  15. data/lib/ruby-next/commands/nextify.rb +64 -22
  16. data/lib/ruby-next/core.rb +39 -22
  17. data/lib/ruby-next/core/array/deconstruct.rb +9 -9
  18. data/lib/ruby-next/core/array/difference_union_intersection.rb +12 -12
  19. data/lib/ruby-next/core/constants/no_matching_pattern_error.rb +3 -3
  20. data/lib/ruby-next/core/enumerable/filter.rb +8 -8
  21. data/lib/ruby-next/core/enumerable/filter_map.rb +25 -25
  22. data/lib/ruby-next/core/enumerable/tally.rb +7 -7
  23. data/lib/ruby-next/core/enumerator/produce.rb +12 -12
  24. data/lib/ruby-next/core/hash/deconstruct_keys.rb +9 -9
  25. data/lib/ruby-next/core/hash/except.rb +11 -0
  26. data/lib/ruby-next/core/hash/merge.rb +8 -8
  27. data/lib/ruby-next/core/kernel/then.rb +2 -2
  28. data/lib/ruby-next/core/proc/compose.rb +11 -11
  29. data/lib/ruby-next/core/string/split.rb +6 -6
  30. data/lib/ruby-next/core/struct/deconstruct.rb +2 -2
  31. data/lib/ruby-next/core/struct/deconstruct_keys.rb +17 -17
  32. data/lib/ruby-next/core/symbol/end_with.rb +4 -4
  33. data/lib/ruby-next/core/symbol/start_with.rb +4 -4
  34. data/lib/ruby-next/core/time/ceil.rb +6 -5
  35. data/lib/ruby-next/core/time/floor.rb +4 -4
  36. data/lib/ruby-next/core/unboundmethod/bind_call.rb +4 -4
  37. data/lib/ruby-next/core_ext.rb +2 -2
  38. data/lib/ruby-next/language.rb +31 -5
  39. data/lib/ruby-next/language/eval.rb +10 -8
  40. data/lib/ruby-next/language/proposed.rb +3 -0
  41. data/lib/ruby-next/language/rewriters/args_forward.rb +24 -20
  42. data/lib/ruby-next/language/rewriters/base.rb +2 -2
  43. data/lib/ruby-next/language/rewriters/endless_method.rb +26 -3
  44. data/lib/ruby-next/language/rewriters/endless_range.rb +1 -0
  45. data/lib/ruby-next/language/rewriters/find_pattern.rb +44 -0
  46. data/lib/ruby-next/language/rewriters/method_reference.rb +2 -1
  47. data/lib/ruby-next/language/rewriters/numbered_params.rb +1 -0
  48. data/lib/ruby-next/language/rewriters/pattern_matching.rb +105 -13
  49. data/lib/ruby-next/language/rewriters/right_hand_assignment.rb +2 -1
  50. data/lib/ruby-next/language/rewriters/runtime.rb +6 -0
  51. data/lib/ruby-next/language/rewriters/runtime/dir.rb +32 -0
  52. data/lib/ruby-next/language/rewriters/safe_navigation.rb +87 -0
  53. data/lib/ruby-next/language/rewriters/shorthand_hash.rb +47 -0
  54. data/lib/ruby-next/language/rewriters/squiggly_heredoc.rb +36 -0
  55. data/lib/ruby-next/language/runtime.rb +3 -2
  56. data/lib/ruby-next/logging.rb +1 -1
  57. data/lib/ruby-next/rubocop.rb +15 -9
  58. data/lib/ruby-next/setup_self.rb +22 -0
  59. data/lib/ruby-next/utils.rb +30 -0
  60. data/lib/ruby-next/version.rb +1 -1
  61. data/lib/uby-next.rb +8 -4
  62. metadata +22 -7
@@ -3,14 +3,16 @@
3
3
  module RubyNext
4
4
  module Language
5
5
  module KernelEval
6
- refine Kernel do
7
- def eval(source, bind = nil, *args)
8
- new_source = ::RubyNext::Language::Runtime.transform(
9
- source,
10
- using: bind&.receiver == TOPLEVEL_BINDING.receiver || bind&.receiver&.is_a?(Module)
11
- )
12
- RubyNext.debug_source(new_source, "(#{caller_locations(1, 1).first})")
13
- super new_source, bind, *args
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
- SYNTAX_PROBE = "obj = Object.new; def obj.foo(...) super(...); end"
8
- MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
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 on_forward_args(node)
14
+ def on_forward_arg(node)
14
15
  context.track! self
15
16
 
16
- replace(node.loc.expression, "(*#{REST}, &#{BLOCK})")
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
- return super(node) unless node.children[2]&.type == :forwarded_args
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
- replace(node.children[2].loc.expression, "*#{REST}, &#{BLOCK}")
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[0..1],
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("2.8.0")
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
@@ -4,6 +4,7 @@ module RubyNext
4
4
  module Language
5
5
  module Rewriters
6
6
  class EndlessRange < Base
7
+ NAME = "endless-range"
7
8
  SYNTAX_PROBE = "[0, 1][1..]"
8
9
  MIN_SUPPORTED_VERSION = Gem::Version.new("2.6.0")
9
10
 
@@ -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("2.7.0")
9
+ MIN_SUPPORTED_VERSION = Gem::Version.new("3.0.0")
9
10
 
10
11
  def on_meth_ref(node)
11
12
  context.track! self
@@ -6,6 +6,7 @@ module RubyNext
6
6
  class NumberedParams < Base
7
7
  using RubyNext
8
8
 
9
+ NAME = "numbered-params"
9
10
  SYNTAX_PROBE = "proc { _1 }.call(1)"
10
11
  MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
11
12
 
@@ -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
- :begin,
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
- rest = arr_rest_items(index, tail.size).then do |r|
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, -(tail.size + 1)).then do |node|
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 arr_rest_items(index, size, arr = s(:lvar, locals[:arr]))
636
+ def arr_slice(lindex, rindex, arr = s(:lvar, locals[:arr]))
552
637
  s(:index,
553
638
  arr,
554
639
  s(:irange,
555
- s(:int, index),
556
- s(:int, -(size + 1))))
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