ruby-next-core 0.13.1 → 0.14.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/README.md +31 -5
  4. data/bin/transform +5 -1
  5. data/lib/.rbnext/2.1/ruby-next/core.rb +7 -1
  6. data/lib/.rbnext/2.1/ruby-next/language.rb +41 -23
  7. data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +2 -2
  8. data/lib/.rbnext/2.3/ruby-next/language/eval.rb +1 -0
  9. data/lib/.rbnext/2.3/ruby-next/language/rewriters/{endless_range.rb → 2.6/endless_range.rb} +0 -0
  10. data/lib/.rbnext/2.3/ruby-next/language/rewriters/{pattern_matching.rb → 2.7/pattern_matching.rb} +121 -34
  11. data/lib/.rbnext/2.3/ruby-next/language/rewriters/3.1/anonymous_block.rb +68 -0
  12. data/lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb +13 -1
  13. data/lib/.rbnext/2.7/ruby-next/core.rb +7 -1
  14. data/lib/.rbnext/2.7/ruby-next/language/rewriters/2.7/pattern_matching.rb +1061 -0
  15. data/lib/ruby-next/commands/nextify.rb +2 -2
  16. data/lib/ruby-next/config.rb +2 -2
  17. data/lib/ruby-next/core/enumerable/compact.rb +22 -0
  18. data/lib/ruby-next/core/integer/try_convert.rb +16 -0
  19. data/lib/ruby-next/core/matchdata/match.rb +9 -0
  20. data/lib/ruby-next/core/refinement/import.rb +60 -0
  21. data/lib/ruby-next/core.rb +7 -1
  22. data/lib/ruby-next/language/eval.rb +1 -0
  23. data/lib/ruby-next/language/rewriters/{numeric_literals.rb → 2.1/numeric_literals.rb} +0 -0
  24. data/lib/ruby-next/language/rewriters/{required_kwargs.rb → 2.1/required_kwargs.rb} +0 -0
  25. data/lib/ruby-next/language/rewriters/{safe_navigation.rb → 2.3/safe_navigation.rb} +0 -0
  26. data/lib/ruby-next/language/rewriters/{squiggly_heredoc.rb → 2.3/squiggly_heredoc.rb} +0 -0
  27. data/lib/ruby-next/language/rewriters/{runtime → 2.4}/dir.rb +0 -0
  28. data/lib/ruby-next/language/rewriters/{endless_range.rb → 2.6/endless_range.rb} +0 -0
  29. data/lib/ruby-next/language/rewriters/{args_forward.rb → 2.7/args_forward.rb} +0 -0
  30. data/lib/ruby-next/language/rewriters/{numbered_params.rb → 2.7/numbered_params.rb} +0 -0
  31. data/lib/ruby-next/language/rewriters/{pattern_matching.rb → 2.7/pattern_matching.rb} +121 -34
  32. data/lib/ruby-next/language/rewriters/{args_forward_leading.rb → 3.0/args_forward_leading.rb} +0 -0
  33. data/lib/ruby-next/language/rewriters/{endless_method.rb → 3.0/endless_method.rb} +15 -12
  34. data/lib/ruby-next/language/rewriters/{find_pattern.rb → 3.0/find_pattern.rb} +1 -3
  35. data/lib/ruby-next/language/rewriters/3.0/in_pattern.rb +22 -0
  36. data/lib/ruby-next/language/rewriters/3.1/anonymous_block.rb +68 -0
  37. data/lib/ruby-next/language/rewriters/3.1/endless_method_command.rb +46 -0
  38. data/lib/ruby-next/language/rewriters/3.1/oneline_pattern_parensless.rb +41 -0
  39. data/lib/ruby-next/language/rewriters/3.1/pin_vars_pattern.rb +50 -0
  40. data/lib/ruby-next/language/rewriters/3.1/refinement_import_methods.rb +60 -0
  41. data/lib/ruby-next/language/rewriters/{shorthand_hash.rb → 3.1/shorthand_hash.rb} +6 -4
  42. data/lib/ruby-next/language/rewriters/base.rb +13 -1
  43. data/lib/ruby-next/language/{edge.rb → rewriters/edge.rb} +0 -0
  44. data/lib/ruby-next/language/rewriters/proposed/bind_vars_pattern.rb +50 -0
  45. data/lib/ruby-next/language/rewriters/{method_reference.rb → proposed/method_reference.rb} +0 -0
  46. data/lib/ruby-next/language/rewriters/proposed.rb +9 -0
  47. data/lib/ruby-next/language/rewriters/runtime.rb +1 -2
  48. data/lib/ruby-next/language.rb +41 -23
  49. data/lib/ruby-next/rubocop.rb +24 -4
  50. data/lib/ruby-next/version.rb +1 -1
  51. metadata +35 -23
  52. data/lib/ruby-next/language/proposed.rb +0 -9
  53. data/lib/ruby-next/language/rewriters/in_pattern.rb +0 -56
@@ -48,11 +48,11 @@ module RubyNext
48
48
  end
49
49
 
50
50
  opts.on("--edge", "Enable edge (master) Ruby features") do |val|
51
- require "ruby-next/language/edge"
51
+ require "ruby-next/language/rewriters/edge"
52
52
  end
53
53
 
54
54
  opts.on("--proposed", "Enable proposed/experimental Ruby features") do |val|
55
- require "ruby-next/language/proposed"
55
+ require "ruby-next/language/rewriters/proposed"
56
56
  end
57
57
 
58
58
  opts.on(
@@ -10,10 +10,10 @@ module RubyNext
10
10
  # Defines last minor version for every major version
11
11
  LAST_MINOR_VERSIONS = {
12
12
  2 => 8, # 2.8 is required for backward compatibility: some gems already uses it
13
- 3 => 0
13
+ 3 => 1
14
14
  }.freeze
15
15
 
16
- LATEST_VERSION = [3, 0].freeze
16
+ LATEST_VERSION = [3, 1].freeze
17
17
 
18
18
  # A virtual version number used for proposed features
19
19
  NEXT_VERSION = "1995.next.0"
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ RubyNext::Core.patch Enumerable, method: :compact, version: "3.1" do
4
+ <<-RUBY
5
+ def compact
6
+ reduce([]) do |acc, val|
7
+ acc << val unless val.nil?
8
+ acc
9
+ end
10
+ end
11
+ RUBY
12
+ end
13
+
14
+ RubyNext::Core.patch Enumerator::Lazy, method: :compact, version: "3.1" do
15
+ <<-RUBY
16
+ def compact
17
+ Enumerator::Lazy.new(self) do |yielder, value|
18
+ yielder << value unless value.nil?
19
+ end
20
+ end
21
+ RUBY
22
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ RubyNext::Core.patch Integer.singleton_class, method: :try_convert, singleton: Integer, version: "3.1" do
4
+ <<-'RUBY'
5
+ def try_convert(val)
6
+ return val if val.is_a?(Integer)
7
+
8
+ if val.respond_to?(:to_int)
9
+ val.to_int.tap do |res|
10
+ next if res.is_a?(Integer) || res.nil?
11
+ raise TypeError, "Can't convert #{res.class} to Integer"
12
+ end
13
+ end
14
+ end
15
+ RUBY
16
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ RubyNext::Core.patch MatchData, method: :match, version: "3.1" do
4
+ <<-RUBY
5
+ def match(index_or_name)
6
+ self[index_or_name]
7
+ end
8
+ RUBY
9
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ # We cannot use refinements here, since Ruby 2.6- doesn't support them in refine modules.
4
+ # So, we use a defined method instead (and transpile source code to use it).
5
+ # NOTE: We have to transpile the source code anyway, since we need to pass a binding.
6
+ RubyNext::Core.singleton_class.module_eval do
7
+ def import_methods(other, bind)
8
+ import = []
9
+
10
+ other.instance_methods(false).each do |mid|
11
+ # check for non-Ruby methods
12
+ meth = other.instance_method(mid)
13
+ location = meth.source_location
14
+
15
+ if location.nil? || location.first.match?(/(<internal:|resource:\/truffleruby\/core)/)
16
+ raise ArgumentError, "Can't import method: #{other}##{mid} from #{location}"
17
+ end
18
+
19
+ source_file, lineno = *location
20
+
21
+ raise ArgumentError, "Can't import dynamicly added methods: #{other}##{mid}" unless File.file?(source_file)
22
+
23
+ lines = File.open(source_file).readlines
24
+
25
+ buffer = []
26
+
27
+ lines[(lineno - 1)..-1].each do |line|
28
+ buffer << line + "\n"
29
+
30
+ begin
31
+ if defined?(::RubyNext::Language) && ::RubyNext::Language.runtime?
32
+ new_source = ::RubyNext::Language.transform(buffer.join, rewriters: RubyNext::Language.current_rewriters, using: false)
33
+ # Transformed successfully => valid method => evaluate transpiled code
34
+ import << [new_source, source_file, lineno]
35
+ buffer.clear
36
+ break
37
+ end
38
+
39
+ # Borrowed from https://github.com/banister/method_source/blob/81d039c966ffd95d26e12eb2e205c0eb8377f49d/lib/method_source/code_helpers.rb#L66
40
+ catch(:valid) do
41
+ eval("BEGIN{throw :valid}\nObject.new.instance_eval { #{buffer.join} }") # rubocop:disable all
42
+ end
43
+ break
44
+ rescue SyntaxError
45
+ end
46
+ end
47
+
48
+ import << [buffer.join, source_file, lineno] unless buffer.empty?
49
+ end
50
+
51
+ import.each do |(definition, file, lino)|
52
+ Kernel.eval definition, bind, file, lino
53
+ end
54
+
55
+ # Copy constants (they could be accessed from methods)
56
+ other.constants.each do |name|
57
+ Kernel.eval "#{name} = #{other}::#{name}", bind
58
+ end
59
+ end
60
+ end
@@ -62,7 +62,7 @@ module RubyNext
62
62
  mod_name = singleton? ? singleton.name : mod.name
63
63
  camelized_method_name = method_name.to_s.split("_").map(&:capitalize).join
64
64
 
65
- "#{mod_name}#{camelized_method_name}".gsub(/\W/, "")
65
+ "#{mod_name}#{camelized_method_name}".gsub(/\W/, "") # rubocop:disable Performance/StringReplacement
66
66
  end
67
67
 
68
68
  def build_location(trace_locations)
@@ -173,6 +173,8 @@ require "ruby-next/core/unboundmethod/bind_call"
173
173
  require "ruby-next/core/time/floor"
174
174
  require "ruby-next/core/time/ceil"
175
175
 
176
+ require "ruby-next/core/refinement/import"
177
+
176
178
  # Core extensions required for pattern matching
177
179
  # Required for pattern matching with refinements
178
180
  unless defined?(NoMatchingPatternError)
@@ -191,6 +193,10 @@ require "ruby-next/core/hash/except"
191
193
 
192
194
  require "ruby-next/core/array/intersect"
193
195
 
196
+ require "ruby-next/core/matchdata/match"
197
+ require "ruby-next/core/enumerable/compact"
198
+ require "ruby-next/core/integer/try_convert"
199
+
194
200
  # Generate refinements
195
201
  RubyNext.module_eval do
196
202
  RubyNext::Core.patches.refined.each do |mod, patches|
@@ -37,6 +37,7 @@ module RubyNext
37
37
 
38
38
  source = args.shift
39
39
  new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
40
+
40
41
  RubyNext.debug_source(new_source, "(#{caller_locations(1, 1).first})")
41
42
  super new_source, *args
42
43
  end
@@ -243,6 +243,7 @@ module RubyNext
243
243
 
244
244
  @deconstructed_keys = {}
245
245
  @predicates = Predicates::CaseIn.new
246
+ @lvars = []
246
247
 
247
248
  matchee_ast =
248
249
  s(:begin, s(:lvasgn, MATCHEE, node.children[0]))
@@ -272,6 +273,7 @@ module RubyNext
272
273
 
273
274
  @deconstructed_keys = {}
274
275
  @predicates = Predicates::Noop.new
276
+ @lvars = []
275
277
 
276
278
  matchee =
277
279
  s(:begin, s(:lvasgn, MATCHEE, node.children[0]))
@@ -282,10 +284,12 @@ module RubyNext
282
284
  arr: MATCHEE_ARR,
283
285
  hash: MATCHEE_HASH
284
286
  ) do
285
- send(
286
- :"#{node.children[1].type}_clause",
287
- node.children[1]
288
- ).then do |node|
287
+ with_declared_locals do
288
+ send(
289
+ :"#{node.children[1].type}_clause",
290
+ node.children[1]
291
+ )
292
+ end.then do |node|
289
293
  s(:begin,
290
294
  s(:or,
291
295
  node,
@@ -306,6 +310,41 @@ module RubyNext
306
310
 
307
311
  alias on_in_match on_match_pattern
308
312
 
313
+ def on_match_pattern_p(node)
314
+ context.track! self
315
+
316
+ @deconstructed_keys = {}
317
+ @predicates = Predicates::Noop.new
318
+ @lvars = []
319
+
320
+ matchee =
321
+ s(:begin, s(:lvasgn, MATCHEE, node.children[0]))
322
+
323
+ pattern =
324
+ locals.with(
325
+ matchee: MATCHEE,
326
+ arr: MATCHEE_ARR,
327
+ hash: MATCHEE_HASH
328
+ ) do
329
+ with_declared_locals do
330
+ send(
331
+ :"#{node.children[1].type}_clause",
332
+ node.children[1]
333
+ )
334
+ end
335
+ end
336
+
337
+ node.updated(
338
+ :and,
339
+ [
340
+ matchee,
341
+ pattern
342
+ ]
343
+ ).tap do |new_node|
344
+ replace(node.loc.expression, inline_blocks(unparse(new_node)))
345
+ end
346
+ end
347
+
309
348
  private
310
349
 
311
350
  def rewrite_case_in!(node, matchee, new_node)
@@ -363,13 +402,15 @@ module RubyNext
363
402
  def build_when_clause(clause)
364
403
  predicates.reset!
365
404
  [
366
- with_guard(
367
- send(
368
- :"#{clause.children[0].type}_clause",
369
- clause.children[0]
370
- ),
371
- clause.children[1] # guard
372
- ),
405
+ with_declared_locals do
406
+ with_guard(
407
+ send(
408
+ :"#{clause.children[0].type}_clause",
409
+ clause.children[0]
410
+ ),
411
+ clause.children[1] # guard
412
+ )
413
+ end,
373
414
  process(clause.children[2] || s(:nil)) # expression
374
415
  ].then do |children|
375
416
  s(:when, *children)
@@ -407,13 +448,14 @@ module RubyNext
407
448
  end
408
449
 
409
450
  def match_var_clause(node, left = s(:lvar, locals[:matchee]))
410
- return s(:true) if node.children[0] == :_
451
+ var = node.children[0]
452
+ return s(:true) if var == :_
411
453
 
412
- check_match_var_alternation! node.children[0]
454
+ check_match_var_alternation!(var)
413
455
 
414
456
  s(:begin,
415
457
  s(:or,
416
- s(:begin, s(:lvasgn, node.children[0], left)),
458
+ s(:begin, build_var_assignment(var, left)),
417
459
  s(:true)))
418
460
  end
419
461
 
@@ -508,11 +550,10 @@ module RubyNext
508
550
  def array_find(head, *nodes, tail)
509
551
  index = s(:lvar, :__i__)
510
552
 
511
- match_vars = []
512
-
513
553
  head_match =
514
554
  unless head.children.empty?
515
- match_vars << s(:lvasgn, head.children[0].children[0])
555
+ # we only need to call this to track the lvar usage
556
+ build_var_assignment(head.children[0].children[0])
516
557
 
517
558
  arr_take = s(:send,
518
559
  s(:lvar, locals[:arr]),
@@ -524,16 +565,19 @@ module RubyNext
524
565
 
525
566
  tail_match =
526
567
  unless tail.children.empty?
527
- match_vars << s(:lvasgn, tail.children[0].children[0])
568
+ # we only need to call this to track the lvar usage
569
+ build_var_assignment(tail.children[0].children[0])
528
570
 
529
571
  match_var_clause(tail.children[0], arr_slice(index + nodes.size, -1))
530
572
  end
531
573
 
532
574
  nodes.each do |node|
533
575
  if node.type == :match_var
534
- match_vars << s(:lvasgn, node.children[0])
576
+ # we only need to call this to track the lvar usage
577
+ build_var_assignment(node.children[0])
535
578
  elsif node.type == :match_as
536
- match_vars << s(:lvasgn, node.children[1].children[0])
579
+ # we only need to call this to track the lvar usage
580
+ build_var_assignment(node.children[1].children[0])
537
581
  end
538
582
  end
539
583
 
@@ -561,19 +605,7 @@ module RubyNext
561
605
  s(:args,
562
606
  s(:arg, :_),
563
607
  s(:arg, :__i__)),
564
- pattern).then do |block|
565
- next block if match_vars.empty?
566
-
567
- # We need to declare match vars outside of `find` block
568
- locals_declare = s(:begin, s(:masgn,
569
- s(:mlhs, *match_vars),
570
- s(:nil)))
571
-
572
- s(:begin,
573
- s(:or,
574
- locals_declare,
575
- block))
576
- end
608
+ pattern)
577
609
  end
578
610
 
579
611
  def array_match_rest(index, node, *tail)
@@ -616,6 +648,14 @@ module RubyNext
616
648
  end
617
649
  end
618
650
 
651
+ def find_pattern_array_element(node, index)
652
+ element = arr_item_at(index)
653
+ locals.with(arr: locals[:arr, index]) do
654
+ predicates.push :"i#{index}"
655
+ find_pattern_clause(node, element).tap { predicates.pop }
656
+ end
657
+ end
658
+
619
659
  def hash_pattern_array_element(node, index)
620
660
  element = arr_item_at(index)
621
661
  locals.with(hash: locals[:arr, index]) do
@@ -796,6 +836,15 @@ module RubyNext
796
836
  end
797
837
  end
798
838
 
839
+ def find_pattern_hash_element(node, key)
840
+ element = hash_value_at(key)
841
+ key_index = deconstructed_key(key)
842
+ locals.with(arr: locals[:hash, key_index]) do
843
+ predicates.push :"k#{key_index}"
844
+ find_pattern_clause(node, element).tap { predicates.pop }
845
+ end
846
+ end
847
+
799
848
  def hash_element(head, *tail)
800
849
  send("#{head.type}_hash_element", head).then do |node|
801
850
  next node if tail.empty?
@@ -861,6 +910,10 @@ module RubyNext
861
910
  match_var_clause(child, s(:lvar, locals[:hash]))
862
911
  end
863
912
 
913
+ def pin_hash_element(node, index)
914
+ case_eq_hash_element node.children[0], index
915
+ end
916
+
864
917
  def case_eq_hash_element(node, key)
865
918
  case_eq_clause node, hash_value_at(key)
866
919
  end
@@ -911,6 +964,24 @@ module RubyNext
911
964
  end
912
965
  end
913
966
 
967
+ def with_declared_locals
968
+ lvars.clear
969
+ node = yield
970
+
971
+ return node if lvars.empty?
972
+
973
+ # We need to declare match lvars outside of the outer `find` block,
974
+ # so we do that for that whole pattern
975
+ locals_declare = s(:begin, s(:masgn,
976
+ s(:mlhs, *lvars.uniq.map { s(:lvasgn, _1) }),
977
+ s(:nil)))
978
+
979
+ s(:begin,
980
+ s(:or,
981
+ locals_declare,
982
+ node))
983
+ end
984
+
914
985
  def no_matching_pattern
915
986
  raise_error(
916
987
  :NoMatchingPatternError,
@@ -945,13 +1016,17 @@ module RubyNext
945
1016
 
946
1017
  private
947
1018
 
948
- attr_reader :deconstructed_keys, :predicates
1019
+ attr_reader :deconstructed_keys, :predicates, :lvars
949
1020
 
950
1021
  # Raise SyntaxError if match-var is used within alternation
951
1022
  # https://github.com/ruby/ruby/blob/672213ef1ca2b71312084057e27580b340438796/compile.c#L5900
952
1023
  def check_match_var_alternation!(name)
953
1024
  return unless locals.key?(ALTERNATION_MARKER)
954
1025
 
1026
+ if name.is_a?(::Parser::AST::Node)
1027
+ raise ::SyntaxError, "illegal variable in alternative pattern (#{name.children.first})"
1028
+ end
1029
+
955
1030
  return if name.start_with?("_")
956
1031
 
957
1032
  raise ::SyntaxError, "illegal variable in alternative pattern (#{name})"
@@ -968,6 +1043,18 @@ module RubyNext
968
1043
  def inline_blocks(source)
969
1044
  source.gsub(/(?:do|{) \|_, __i__\|\n\s*([^\n]+)\n\s*(?:end|})/, '{ |_, __i__| \1 }')
970
1045
  end
1046
+
1047
+ # Value could be omitted for mass assignment
1048
+ def build_var_assignment(var, value = nil)
1049
+ unless var.is_a?(::Parser::AST::Node)
1050
+ lvars << var
1051
+ return s(:lvasgn, *[var, value].compact)
1052
+ end
1053
+
1054
+ asign_type = :"#{var.type.to_s[0]}vasgn"
1055
+
1056
+ s(asign_type, *[var.children[0], value].compact)
1057
+ end
971
1058
  end
972
1059
  end
973
1060
  end
@@ -8,14 +8,13 @@ module RubyNext
8
8
  SYNTAX_PROBE = "obj = Object.new; def obj.foo() = 42"
9
9
  MIN_SUPPORTED_VERSION = Gem::Version.new("3.0.0")
10
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
11
+ def on_def(node)
12
+ return process_def(node) if endless?(node)
13
+
14
+ super(node)
16
15
  end
17
16
 
18
- def on_def_e(node)
17
+ def process_def(node)
19
18
  context.track! self
20
19
 
21
20
  replace(node.loc.assignment, "; ")
@@ -33,14 +32,12 @@ module RubyNext
33
32
  )
34
33
  end
35
34
 
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
35
+ def on_defs(node)
36
+ return process_defs(node) if endless?(node)
37
+ super(node)
41
38
  end
42
39
 
43
- def on_defs_e(node)
40
+ def process_defs(node)
44
41
  context.track! self
45
42
 
46
43
  replace(node.loc.assignment, "; ")
@@ -57,6 +54,12 @@ module RubyNext
57
54
  )
58
55
  )
59
56
  end
57
+
58
+ private
59
+
60
+ def endless?(node)
61
+ node.loc.end.nil?
62
+ end
60
63
  end
61
64
  end
62
65
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "ruby-next/language/rewriters/pattern_matching"
4
-
5
3
  module RubyNext
6
4
  module Language
7
5
  module Rewriters
@@ -22,7 +20,7 @@ module RubyNext
22
20
  end
23
21
  end
24
22
 
25
- def on_in_match(node)
23
+ def on_match_pattern(node)
26
24
  @has_find_pattern = false
27
25
  process_regular_node(node).then do |new_node|
28
26
  return new_node unless has_find_pattern
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ using RubyNext
7
+
8
+ # Separate pattern matching rewriter for Ruby 2.7 to
9
+ # transpile only `in` patterns
10
+ class InPattern < PatternMatching
11
+ NAME = "pattern-matching-in"
12
+ SYNTAX_PROBE = "1 in 2"
13
+ MIN_SUPPORTED_VERSION = Gem::Version.new("3.0.0")
14
+
15
+ # Make case-match no-op
16
+ def on_case_match(node)
17
+ process_regular_node(node)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ class AnonymousBlock < Base
7
+ NAME = "anonymous-block"
8
+ SYNTAX_PROBE = "obj = Object.new; def obj.foo(&) bar(&); end"
9
+ MIN_SUPPORTED_VERSION = Gem::Version.new("3.1.0")
10
+
11
+ BLOCK = :__block__
12
+
13
+ def on_args(node)
14
+ block = node.children.last
15
+
16
+ return super unless block&.type == :blockarg
17
+ return super unless block.children.first.nil?
18
+
19
+ context.track! self
20
+
21
+ replace(block.loc.expression, "&#{BLOCK}")
22
+
23
+ node.updated(
24
+ :args,
25
+ [
26
+ *node.children.slice(0, node.children.index(block)),
27
+ s(:blockarg, BLOCK)
28
+ ]
29
+ )
30
+ end
31
+
32
+ def on_send(node)
33
+ block = extract_block_pass(node)
34
+ return super unless block&.children == [nil]
35
+
36
+ process_block(node, block)
37
+ end
38
+
39
+ def on_super(node)
40
+ block = extract_block_pass(node)
41
+ return super unless block&.children == [nil]
42
+
43
+ process_block(node, block)
44
+ end
45
+
46
+ private
47
+
48
+ def extract_block_pass(node)
49
+ node.children.find { |child| child.is_a?(::Parser::AST::Node) && child.type == :block_pass }
50
+ end
51
+
52
+ def process_block(node, block)
53
+ replace(block.loc.expression, "&#{BLOCK}")
54
+
55
+ process(
56
+ node.updated(
57
+ nil,
58
+ [
59
+ *node.children.take(node.children.index(block)),
60
+ s(:block_pass, s(:lvar, BLOCK))
61
+ ]
62
+ )
63
+ )
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ class EndlessMethodCommand < EndlessMethod
7
+ NAME = "endless-method-command"
8
+ SYNTAX_PROBE = "obj = Object.new; def obj.foo = puts 'Hello'"
9
+ MIN_SUPPORTED_VERSION = Gem::Version.new("3.1.0")
10
+
11
+ def process_def(node)
12
+ return node unless command?(node)
13
+
14
+ super(node)
15
+ end
16
+
17
+ def process_defs(node)
18
+ return node unless command?(node)
19
+
20
+ super(node)
21
+ end
22
+
23
+ private
24
+
25
+ def command?(node)
26
+ buffer = ::Parser::Source::Buffer.new("(endless-method-rewriter)").tap do |buffer|
27
+ buffer.source = node.loc.expression.source
28
+ end
29
+
30
+ parser30.parse(buffer)
31
+ false
32
+ rescue ::Parser::SyntaxError
33
+ true
34
+ end
35
+
36
+ def parser30
37
+ require "parser/ruby30" unless defined?(::Parser::Ruby30)
38
+
39
+ ::Parser::Ruby30.new(Language::Builder.new).tap do |prs|
40
+ prs.diagnostics.all_errors_are_fatal = true
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end