ruby-next-core 0.9.1 → 0.10.3

Sign up to get free protection for your applications and to get access to all the features.
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 +201 -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 +44 -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 +91 -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