ruby-next-core 0.9.0 → 0.10.2

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -0
  3. data/README.md +20 -6
  4. data/lib/.rbnext/2.3/ruby-next/commands/core_ext.rb +167 -0
  5. data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +198 -0
  6. data/lib/.rbnext/2.3/ruby-next/language/eval.rb +66 -0
  7. data/lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb +121 -0
  8. data/lib/.rbnext/2.3/ruby-next/language/rewriters/endless_range.rb +63 -0
  9. data/lib/.rbnext/2.3/ruby-next/language/rewriters/pattern_matching.rb +944 -0
  10. data/lib/.rbnext/2.3/ruby-next/language/rewriters/right_hand_assignment.rb +107 -0
  11. data/lib/.rbnext/2.3/ruby-next/utils.rb +65 -0
  12. data/lib/ruby-next.rb +8 -6
  13. data/lib/ruby-next/cli.rb +2 -2
  14. data/lib/ruby-next/commands/core_ext.rb +1 -1
  15. data/lib/ruby-next/commands/nextify.rb +41 -10
  16. data/lib/ruby-next/core.rb +27 -21
  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 -6
  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 +1 -1
  38. data/lib/ruby-next/language.rb +30 -6
  39. data/lib/ruby-next/language/proposed.rb +3 -0
  40. data/lib/ruby-next/language/rewriters/args_forward.rb +24 -20
  41. data/lib/ruby-next/language/rewriters/base.rb +1 -1
  42. data/lib/ruby-next/language/rewriters/endless_method.rb +26 -3
  43. data/lib/ruby-next/language/rewriters/endless_range.rb +1 -0
  44. data/lib/ruby-next/language/rewriters/find_pattern.rb +44 -0
  45. data/lib/ruby-next/language/rewriters/method_reference.rb +2 -1
  46. data/lib/ruby-next/language/rewriters/numbered_params.rb +1 -0
  47. data/lib/ruby-next/language/rewriters/pattern_matching.rb +103 -12
  48. data/lib/ruby-next/language/rewriters/right_hand_assignment.rb +74 -11
  49. data/lib/ruby-next/language/rewriters/safe_navigation.rb +87 -0
  50. data/lib/ruby-next/language/rewriters/shorthand_hash.rb +47 -0
  51. data/lib/ruby-next/language/rewriters/squiggly_heredoc.rb +36 -0
  52. data/lib/ruby-next/logging.rb +1 -1
  53. data/lib/ruby-next/rubocop.rb +15 -9
  54. data/lib/ruby-next/setup_self.rb +22 -0
  55. data/lib/ruby-next/version.rb +1 -1
  56. data/lib/uby-next.rb +8 -4
  57. metadata +21 -7
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  RubyNext::Core.patch UnboundMethod, method: :bind_call, version: "2.7" do
4
- <<~'RUBY'
5
- def bind_call(receiver, *args, &block)
6
- bind(receiver).call(*args, &block)
7
- end
4
+ <<-RUBY
5
+ def bind_call(receiver, *args, &block)
6
+ bind(receiver).call(*args, &block)
7
+ end
8
8
  RUBY
9
9
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "core"
3
+ require "ruby-next/core"
4
4
 
5
5
  # Monkey-patch core classes using the same patches as for refinements
6
6
  RubyNext::Core.patches.extensions.each do |mod, patches|
@@ -23,6 +23,8 @@ module RubyNext
23
23
  require "ruby-next/language/parser"
24
24
  require "ruby-next/language/unparser"
25
25
 
26
+ RewriterNotFoundError = Class.new(StandardError)
27
+
26
28
  class TransformContext
27
29
  attr_reader :versions, :use_ruby_next
28
30
 
@@ -85,12 +87,6 @@ module RubyNext
85
87
  def runtime!
86
88
  require "ruby-next/language/rewriters/runtime"
87
89
 
88
- if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7.0") && !defined?(Unparser::Emitter::CaseMatch)
89
- RubyNext.warn "Ruby Next fallbacks to \"rewrite\" transpiling mode since Unparser doesn't support 2.7 AST yet.\n" \
90
- "See https://github.com/mbj/unparser/pull/142"
91
- self.mode = :rewrite
92
- end
93
-
94
90
  @runtime = true
95
91
  end
96
92
 
@@ -104,6 +100,13 @@ module RubyNext
104
100
  else
105
101
  regenerate(*args, **kwargs)
106
102
  end
103
+ rescue Unparser::UnknownNodeError
104
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7.0")
105
+ RubyNext.warn "Ruby Next fallbacks to \"rewrite\" transpiling mode since Unparser doesn't support 2.7+ AST yet.\n" \
106
+ "See https://github.com/mbj/unparser/pull/142"
107
+ self.mode = :rewrite
108
+ end
109
+ rewrite(*args, **kwargs)
107
110
  end
108
111
 
109
112
  def regenerate(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new)
@@ -149,6 +152,16 @@ module RubyNext
149
152
  @current_rewriters ||= rewriters.select(&:unsupported_syntax?)
150
153
  end
151
154
 
155
+ # This method guarantees that rewriters will be returned in order they defined in Language module
156
+ def select_rewriters(*names)
157
+ rewriters_delta = names - rewriters.map { |rewriter| rewriter::NAME }
158
+ if rewriters_delta.any?
159
+ raise RewriterNotFoundError, "Rewriters not found: #{rewriters_delta.join(",")}"
160
+ end
161
+
162
+ rewriters.select { |rewriter| names.include?(rewriter::NAME) }
163
+ end
164
+
152
165
  private
153
166
 
154
167
  attr_writer :watch_dirs
@@ -160,6 +173,12 @@ module RubyNext
160
173
 
161
174
  require "ruby-next/language/rewriters/base"
162
175
 
176
+ require "ruby-next/language/rewriters/squiggly_heredoc"
177
+ rewriters << Rewriters::SquigglyHeredoc
178
+
179
+ require "ruby-next/language/rewriters/safe_navigation"
180
+ rewriters << Rewriters::SafeNavigation
181
+
163
182
  require "ruby-next/language/rewriters/args_forward"
164
183
  rewriters << Rewriters::ArgsForward
165
184
 
@@ -169,6 +188,11 @@ module RubyNext
169
188
  require "ruby-next/language/rewriters/pattern_matching"
170
189
  rewriters << Rewriters::PatternMatching
171
190
 
191
+ # Must be added after general pattern matching rewriter to become
192
+ # no-op in Ruby <2.7
193
+ require "ruby-next/language/rewriters/find_pattern"
194
+ rewriters << Rewriters::FindPattern
195
+
172
196
  # Put endless range in the end, 'cause Parser fails to parse it in
173
197
  # pattern matching
174
198
  require "ruby-next/language/rewriters/endless_range"
@@ -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("3.0.0")
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)
@@ -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) ===============
@@ -847,6 +932,12 @@ module RubyNext
847
932
 
848
933
  deconstructed_keys[key] = :"k#{deconstructed_keys.size}"
849
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
850
941
  end
851
942
  end
852
943
  end