ruby-next-core 0.15.1 → 0.15.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fc36fd395eedfde5c29e65f1f3a75c683630d5111349b3070d3b4e6d200477ad
4
- data.tar.gz: e53e28aec62f7e425a98ba1eeb9591778ca1dbab13a0390113fc7440aab9663d
3
+ metadata.gz: 6020fe263af1a0313e82b5633c45ee76950e7d39bd1f4e70f9d2613eb6ba3fec
4
+ data.tar.gz: 3a1c6da894f68e5e703f0515239bf342852d45f4f5672f68f8fe4011a0e76c57
5
5
  SHA512:
6
- metadata.gz: 50069c2ef6b59005d7efc640e1d84a74e17e4e385aaadce3ac54c2e3e061c7f71b727943140899210af997196f0bdf5a1157b86b97ce7af3de307e68a1cffe12
7
- data.tar.gz: edf464100a254380bee320c586f48afc35782027303840fd319f32260ac5cf771c6f349d7447b9ed2f8a55bab424395664099dbd58600a05ffcd79e49080509f
6
+ metadata.gz: dffe0bf9d32e02cd71aee8f5818d4413a62d96c8d595706c476bee2990823cf960240162b79d51d62036d2bb4a0076b46cba5af93c58760064aae165a82cd6e4
7
+ data.tar.gz: 22879525ffbbbd38921b096ef909ad79d5a87d726dae39f6a108aea2dc82ae5605e6a8e1bd9a190689e0a5da66fc897c6d8d22590f9a237416cd9a79aca43a38
data/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.15.3 (2022-10-16)
6
+
7
+ - Fix handling nested const patterns. ([@palkan][])
8
+
9
+ ## 0.15.2 (2022-08-02)
10
+
11
+ - Fix loading transpiled in TruffleRuby. ([@palkan][])
12
+
13
+ TruffleRuby doesn't support all Ruby 3.0 features yet, so we should treat as an older Ruby.
14
+
15
+ - Use `ruby2_keywords` when transpiling arguments forwarding. ([@palkan][])
16
+
17
+ That makes transpiled code forward compatible with the modern Ruby versions.
18
+
5
19
  ## 0.15.1 (2022-04-05)
6
20
 
7
21
  - Fix transpiling `rescue` within nested blocks. ([@palkan][])
@@ -125,7 +125,7 @@ module RubyNext
125
125
 
126
126
  def patch(*__rest__, &__block__)
127
127
  patches << Patch.new(*__rest__, &__block__)
128
- end
128
+ end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :patch)
129
129
 
130
130
  # Inject `using RubyNext` at the top of the source code
131
131
  def inject!(contents)
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ class ArgsForward < Base
7
+ NAME = "args-forward"
8
+ SYNTAX_PROBE = "obj = Object.new; def obj.foo(...) super(...); end"
9
+ MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
10
+
11
+ REST = :__rest__
12
+ BLOCK = :__block__
13
+
14
+ def on_args(node)
15
+ farg = node.children.find { |child| child.is_a?(::Parser::AST::Node) && child.type == :forward_arg }
16
+ return unless farg
17
+
18
+ context.track! self
19
+
20
+ node = super(node)
21
+
22
+ replace(farg.loc.expression, "*#{REST}, &#{BLOCK}")
23
+
24
+ node.updated(
25
+ :args,
26
+ [
27
+ *node.children.slice(0, node.children.index(farg)),
28
+ s(:restarg, REST),
29
+ s(:blockarg, BLOCK)
30
+ ]
31
+ )
32
+ end
33
+
34
+ def on_send(node)
35
+ fargs = extract_fargs(node)
36
+ return super(node) unless fargs
37
+
38
+ process_fargs(node, fargs)
39
+ end
40
+
41
+ def on_super(node)
42
+ fargs = extract_fargs(node)
43
+ return super(node) unless fargs
44
+
45
+ process_fargs(node, fargs)
46
+ end
47
+
48
+ def on_def(node)
49
+ return super unless forward_arg?(node.children[1])
50
+
51
+ new_node = super
52
+
53
+ name = node.children[0]
54
+
55
+ insert_after(node.loc.expression, "; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :#{name})")
56
+
57
+ s(:begin,
58
+ new_node,
59
+ ruby2_keywords_node(nil, name))
60
+ end
61
+
62
+ def on_defs(node)
63
+ return super unless forward_arg?(node.children[2])
64
+
65
+ new_node = super
66
+
67
+ receiver = node.children[0]
68
+ name = node.children[1]
69
+
70
+ # Using self.ruby2_keywords :name results in undefined method error,
71
+ # singleton_class works as expected
72
+ receiver = s(:send, nil, :singleton_class) if receiver.type == :self
73
+
74
+ receiver_name =
75
+ case receiver.type
76
+ when :send
77
+ receiver.children[1]
78
+ when :const
79
+ receiver.children[1]
80
+ end
81
+
82
+ insert_after(node.loc.expression, "; #{receiver_name}.respond_to?(:ruby2_keywords, true) && (#{receiver_name}.send(:ruby2_keywords, :#{name}))")
83
+
84
+ s(:begin,
85
+ new_node,
86
+ ruby2_keywords_node(receiver, name))
87
+ end
88
+
89
+ private
90
+
91
+ def ruby2_keywords_node(receiver, name)
92
+ s(:and,
93
+ s(:send, receiver, :respond_to?,
94
+ s(:sym, :ruby2_keywords), s(:true)),
95
+ s(:begin,
96
+ s(:send, receiver, :send,
97
+ s(:sym, :ruby2_keywords),
98
+ s(:sym, name))))
99
+ end
100
+
101
+ def forward_arg?(args)
102
+ return false unless ((((__safe_lvar__ = args) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.children)
103
+
104
+ args.children.any? { |arg| arg.type == :forward_arg }
105
+ end
106
+
107
+ def extract_fargs(node)
108
+ node.children.find { |child| child.is_a?(::Parser::AST::Node) && child.type == :forwarded_args }
109
+ end
110
+
111
+ def process_fargs(node, fargs)
112
+ replace(fargs.loc.expression, "*#{REST}, &#{BLOCK}")
113
+
114
+ process(
115
+ node.updated(
116
+ nil,
117
+ [
118
+ *node.children.take(node.children.index(fargs)),
119
+ *forwarded_args
120
+ ]
121
+ )
122
+ )
123
+ end
124
+
125
+ def forwarded_args
126
+ [
127
+ s(:splat, s(:lvar, REST)),
128
+ s(:block_pass, s(:lvar, BLOCK))
129
+ ]
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -425,15 +425,15 @@ module RubyNext
425
425
  s(:begin,
426
426
  s(:and,
427
427
  node,
428
- send(:"#{pattern.type}_clause", pattern)))
428
+ send(:"#{pattern.type}_clause", pattern, right)))
429
429
  end
430
430
  end
431
431
 
432
- def match_alt_clause(node)
432
+ def match_alt_clause(node, matchee = s(:lvar, locals[:matchee]))
433
433
  children = locals.with(ALTERNATION_MARKER => true) do
434
434
  node.children.map.with_index do |child, i|
435
435
  predicates.terminate! if i == 1
436
- send :"#{child.type}_clause", child
436
+ send :"#{child.type}_clause", child, matchee
437
437
  end
438
438
  end
439
439
  s(:begin, s(:or, *children))
@@ -663,6 +663,14 @@ module RubyNext
663
663
  end
664
664
  end
665
665
 
666
+ def const_pattern_array_element(node, index)
667
+ element = arr_item_at(index)
668
+ locals.with(arr: locals[:arr, index]) do
669
+ predicates.push :"i#{index}"
670
+ const_pattern_clause(node, element).tap { predicates.pop }
671
+ end
672
+ end
673
+
666
674
  def match_alt_array_element(node, index)
667
675
  children = node.children.map do |child, i|
668
676
  send :"#{child.type}_array_element", child, index
@@ -671,11 +679,19 @@ module RubyNext
671
679
  end
672
680
 
673
681
  def match_var_array_element(node, index)
674
- match_var_clause(node, arr_item_at(index))
682
+ element = arr_item_at(index)
683
+ locals.with(arr: locals[:arr, index]) do
684
+ predicates.push :"i#{index}"
685
+ match_var_clause(node, element).tap { predicates.pop }
686
+ end
675
687
  end
676
688
 
677
689
  def match_as_array_element(node, index)
678
- match_as_clause(node, arr_item_at(index))
690
+ element = arr_item_at(index)
691
+ locals.with(arr: locals[:arr, index]) do
692
+ predicates.push :"i#{index}"
693
+ match_as_clause(node, element).tap { predicates.pop }
694
+ end
679
695
  end
680
696
 
681
697
  def pin_array_element(node, index)
@@ -844,6 +860,15 @@ module RubyNext
844
860
  end
845
861
  end
846
862
 
863
+ def const_pattern_hash_element(node, key)
864
+ element = hash_value_at(key)
865
+ key_index = deconstructed_key(key)
866
+ locals.with(hash: locals[:hash, key_index]) do
867
+ predicates.push :"k#{key_index}"
868
+ const_pattern_clause(node, element).tap { predicates.pop }
869
+ end
870
+ end
871
+
847
872
  def hash_element(head, *tail)
848
873
  send("#{head.type}_hash_element", head).then do |node|
849
874
  next node if tail.empty?
@@ -884,12 +909,22 @@ module RubyNext
884
909
  end
885
910
 
886
911
  def match_as_hash_element(node, key)
887
- match_as_clause(node, hash_value_at(key))
912
+ element = hash_value_at(key)
913
+ key_index = deconstructed_key(key)
914
+ locals.with(hash: locals[:hash, key_index]) do
915
+ predicates.push :"k#{key_index}"
916
+ match_as_clause(node, element).tap { predicates.pop }
917
+ end
888
918
  end
889
919
 
890
920
  def match_var_hash_element(node, key = nil)
891
921
  key ||= node.children[0]
892
- match_var_clause(node, hash_value_at(key))
922
+ element = hash_value_at(key)
923
+ key_index = deconstructed_key(key)
924
+ locals.with(hash: locals[:hash, key_index]) do
925
+ predicates.push :"k#{key_index}"
926
+ match_var_clause(node, element).tap { predicates.pop }
927
+ end
893
928
  end
894
929
 
895
930
  def match_nil_pattern_hash_element(node, _key = nil)
@@ -125,7 +125,7 @@ module RubyNext
125
125
 
126
126
  def patch(*__rest__, &__block__)
127
127
  patches << Patch.new(*__rest__, &__block__)
128
- end
128
+ end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :patch)
129
129
 
130
130
  # Inject `using RubyNext` at the top of the source code
131
131
  def inject!(contents)
@@ -425,15 +425,15 @@ module RubyNext
425
425
  s(:begin,
426
426
  s(:and,
427
427
  node,
428
- send(:"#{pattern.type}_clause", pattern)))
428
+ send(:"#{pattern.type}_clause", pattern, right)))
429
429
  end
430
430
  end
431
431
 
432
- def match_alt_clause(node)
432
+ def match_alt_clause(node, matchee = s(:lvar, locals[:matchee]))
433
433
  children = locals.with(ALTERNATION_MARKER => true) do
434
434
  node.children.map.with_index do |child, i|
435
435
  predicates.terminate! if i == 1
436
- send :"#{child.type}_clause", child
436
+ send :"#{child.type}_clause", child, matchee
437
437
  end
438
438
  end
439
439
  s(:begin, s(:or, *children))
@@ -663,6 +663,14 @@ module RubyNext
663
663
  end
664
664
  end
665
665
 
666
+ def const_pattern_array_element(node, index)
667
+ element = arr_item_at(index)
668
+ locals.with(arr: locals[:arr, index]) do
669
+ predicates.push :"i#{index}"
670
+ const_pattern_clause(node, element).tap { predicates.pop }
671
+ end
672
+ end
673
+
666
674
  def match_alt_array_element(node, index)
667
675
  children = node.children.map do |child, i|
668
676
  send :"#{child.type}_array_element", child, index
@@ -671,11 +679,19 @@ module RubyNext
671
679
  end
672
680
 
673
681
  def match_var_array_element(node, index)
674
- match_var_clause(node, arr_item_at(index))
682
+ element = arr_item_at(index)
683
+ locals.with(arr: locals[:arr, index]) do
684
+ predicates.push :"i#{index}"
685
+ match_var_clause(node, element).tap { predicates.pop }
686
+ end
675
687
  end
676
688
 
677
689
  def match_as_array_element(node, index)
678
- match_as_clause(node, arr_item_at(index))
690
+ element = arr_item_at(index)
691
+ locals.with(arr: locals[:arr, index]) do
692
+ predicates.push :"i#{index}"
693
+ match_as_clause(node, element).tap { predicates.pop }
694
+ end
679
695
  end
680
696
 
681
697
  def pin_array_element(node, index)
@@ -844,6 +860,15 @@ module RubyNext
844
860
  end
845
861
  end
846
862
 
863
+ def const_pattern_hash_element(node, key)
864
+ element = hash_value_at(key)
865
+ key_index = deconstructed_key(key)
866
+ locals.with(hash: locals[:hash, key_index]) do
867
+ predicates.push :"k#{key_index}"
868
+ const_pattern_clause(node, element).tap { predicates.pop }
869
+ end
870
+ end
871
+
847
872
  def hash_element(head, *tail)
848
873
  send("#{head.type}_hash_element", head).then do |node|
849
874
  next node if tail.empty?
@@ -884,12 +909,22 @@ module RubyNext
884
909
  end
885
910
 
886
911
  def match_as_hash_element(node, key)
887
- match_as_clause(node, hash_value_at(key))
912
+ element = hash_value_at(key)
913
+ key_index = deconstructed_key(key)
914
+ locals.with(hash: locals[:hash, key_index]) do
915
+ predicates.push :"k#{key_index}"
916
+ match_as_clause(node, element).tap { predicates.pop }
917
+ end
888
918
  end
889
919
 
890
920
  def match_var_hash_element(node, key = nil)
891
921
  key ||= node.children[0]
892
- match_var_clause(node, hash_value_at(key))
922
+ element = hash_value_at(key)
923
+ key_index = deconstructed_key(key)
924
+ locals.with(hash: locals[:hash, key_index]) do
925
+ predicates.push :"k#{key_index}"
926
+ match_var_clause(node, element).tap { predicates.pop }
927
+ end
893
928
  end
894
929
 
895
930
  def match_nil_pattern_hash_element(node, _key = nil)
data/lib/ruby-next/cli.rb CHANGED
@@ -24,9 +24,6 @@ module RubyNext
24
24
  "core_ext" => Commands::CoreExt
25
25
  }.freeze
26
26
 
27
- def initialize
28
- end
29
-
30
27
  def run(args = ARGV)
31
28
  maybe_print_version(args)
32
29
 
@@ -19,8 +19,8 @@ module RubyNext
19
19
  NEXT_VERSION = "1995.next.0"
20
20
 
21
21
  class << self
22
- # TruffleRuby claims it's 2.7.2 compatible but...
23
- if defined?(TruffleRuby) && ::RUBY_VERSION =~ /^2\.7/
22
+ # TruffleRuby claims its RUBY_VERSION to be X.Y while not supporting all the features
23
+ if defined?(TruffleRuby)
24
24
  def current_ruby_version
25
25
  "2.6.5"
26
26
  end
@@ -45,8 +45,65 @@ module RubyNext
45
45
  process_fargs(node, fargs)
46
46
  end
47
47
 
48
+ def on_def(node)
49
+ return super unless forward_arg?(node.children[1])
50
+
51
+ new_node = super
52
+
53
+ name = node.children[0]
54
+
55
+ insert_after(node.loc.expression, "; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :#{name})")
56
+
57
+ s(:begin,
58
+ new_node,
59
+ ruby2_keywords_node(nil, name))
60
+ end
61
+
62
+ def on_defs(node)
63
+ return super unless forward_arg?(node.children[2])
64
+
65
+ new_node = super
66
+
67
+ receiver = node.children[0]
68
+ name = node.children[1]
69
+
70
+ # Using self.ruby2_keywords :name results in undefined method error,
71
+ # singleton_class works as expected
72
+ receiver = s(:send, nil, :singleton_class) if receiver.type == :self
73
+
74
+ receiver_name =
75
+ case receiver.type
76
+ when :send
77
+ receiver.children[1]
78
+ when :const
79
+ receiver.children[1]
80
+ end
81
+
82
+ insert_after(node.loc.expression, "; #{receiver_name}.respond_to?(:ruby2_keywords, true) && (#{receiver_name}.send(:ruby2_keywords, :#{name}))")
83
+
84
+ s(:begin,
85
+ new_node,
86
+ ruby2_keywords_node(receiver, name))
87
+ end
88
+
48
89
  private
49
90
 
91
+ def ruby2_keywords_node(receiver, name)
92
+ s(:and,
93
+ s(:send, receiver, :respond_to?,
94
+ s(:sym, :ruby2_keywords), s(:true)),
95
+ s(:begin,
96
+ s(:send, receiver, :send,
97
+ s(:sym, :ruby2_keywords),
98
+ s(:sym, name))))
99
+ end
100
+
101
+ def forward_arg?(args)
102
+ return false unless args&.children
103
+
104
+ args.children.any? { |arg| arg.type == :forward_arg }
105
+ end
106
+
50
107
  def extract_fargs(node)
51
108
  node.children.find { |child| child.is_a?(::Parser::AST::Node) && child.type == :forwarded_args }
52
109
  end
@@ -425,15 +425,15 @@ module RubyNext
425
425
  s(:begin,
426
426
  s(:and,
427
427
  node,
428
- send(:"#{pattern.type}_clause", pattern)))
428
+ send(:"#{pattern.type}_clause", pattern, right)))
429
429
  end
430
430
  end
431
431
 
432
- def match_alt_clause(node)
432
+ def match_alt_clause(node, matchee = s(:lvar, locals[:matchee]))
433
433
  children = locals.with(ALTERNATION_MARKER => true) do
434
434
  node.children.map.with_index do |child, i|
435
435
  predicates.terminate! if i == 1
436
- send :"#{child.type}_clause", child
436
+ send :"#{child.type}_clause", child, matchee
437
437
  end
438
438
  end
439
439
  s(:begin, s(:or, *children))
@@ -663,6 +663,14 @@ module RubyNext
663
663
  end
664
664
  end
665
665
 
666
+ def const_pattern_array_element(node, index)
667
+ element = arr_item_at(index)
668
+ locals.with(arr: locals[:arr, index]) do
669
+ predicates.push :"i#{index}"
670
+ const_pattern_clause(node, element).tap { predicates.pop }
671
+ end
672
+ end
673
+
666
674
  def match_alt_array_element(node, index)
667
675
  children = node.children.map do |child, i|
668
676
  send :"#{child.type}_array_element", child, index
@@ -671,11 +679,19 @@ module RubyNext
671
679
  end
672
680
 
673
681
  def match_var_array_element(node, index)
674
- match_var_clause(node, arr_item_at(index))
682
+ element = arr_item_at(index)
683
+ locals.with(arr: locals[:arr, index]) do
684
+ predicates.push :"i#{index}"
685
+ match_var_clause(node, element).tap { predicates.pop }
686
+ end
675
687
  end
676
688
 
677
689
  def match_as_array_element(node, index)
678
- match_as_clause(node, arr_item_at(index))
690
+ element = arr_item_at(index)
691
+ locals.with(arr: locals[:arr, index]) do
692
+ predicates.push :"i#{index}"
693
+ match_as_clause(node, element).tap { predicates.pop }
694
+ end
679
695
  end
680
696
 
681
697
  def pin_array_element(node, index)
@@ -844,6 +860,15 @@ module RubyNext
844
860
  end
845
861
  end
846
862
 
863
+ def const_pattern_hash_element(node, key)
864
+ element = hash_value_at(key)
865
+ key_index = deconstructed_key(key)
866
+ locals.with(hash: locals[:hash, key_index]) do
867
+ predicates.push :"k#{key_index}"
868
+ const_pattern_clause(node, element).tap { predicates.pop }
869
+ end
870
+ end
871
+
847
872
  def hash_element(head, *tail)
848
873
  send("#{head.type}_hash_element", head).then do |node|
849
874
  next node if tail.empty?
@@ -884,12 +909,22 @@ module RubyNext
884
909
  end
885
910
 
886
911
  def match_as_hash_element(node, key)
887
- match_as_clause(node, hash_value_at(key))
912
+ element = hash_value_at(key)
913
+ key_index = deconstructed_key(key)
914
+ locals.with(hash: locals[:hash, key_index]) do
915
+ predicates.push :"k#{key_index}"
916
+ match_as_clause(node, element).tap { predicates.pop }
917
+ end
888
918
  end
889
919
 
890
920
  def match_var_hash_element(node, key = nil)
891
921
  key ||= node.children[0]
892
- match_var_clause(node, hash_value_at(key))
922
+ element = hash_value_at(key)
923
+ key_index = deconstructed_key(key)
924
+ locals.with(hash: locals[:hash, key_index]) do
925
+ predicates.push :"k#{key_index}"
926
+ match_var_clause(node, element).tap { predicates.pop }
927
+ end
893
928
  end
894
929
 
895
930
  def match_nil_pattern_hash_element(node, _key = nil)
@@ -12,7 +12,7 @@ module RubyNext
12
12
  return if File.directory?(target_dir)
13
13
 
14
14
  Dir.chdir(root_dir) do
15
- unless system("bundle exec ruby-next nextify ./#{lib_dir} -o #{target_dir} --min-version=#{RUBY_VERSION} > /dev/null 2>&1")
15
+ unless system("bundle exec ruby-next nextify ./#{lib_dir} -o #{target_dir} --min-version=#{RubyNext.current_ruby_version} > /dev/null 2>&1")
16
16
  RubyNext.warn "Traspiled files are missing in: #{target_dir}. \n" \
17
17
  "Make sure you have gem 'ruby-next' in your Gemfile to auto-transpile the required files from source on load. " \
18
18
  "Otherwise the code from #{root_dir} may not work correctly."
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyNext
4
- VERSION = "0.15.1"
4
+ VERSION = "0.15.3"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-next-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.1
4
+ version: 0.15.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-05 00:00:00.000000000 Z
11
+ date: 2022-10-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-next-parser
@@ -62,6 +62,7 @@ files:
62
62
  - lib/.rbnext/2.3/ruby-next/commands/nextify.rb
63
63
  - lib/.rbnext/2.3/ruby-next/language/eval.rb
64
64
  - lib/.rbnext/2.3/ruby-next/language/rewriters/2.6/endless_range.rb
65
+ - lib/.rbnext/2.3/ruby-next/language/rewriters/2.7/args_forward.rb
65
66
  - lib/.rbnext/2.3/ruby-next/language/rewriters/2.7/pattern_matching.rb
66
67
  - lib/.rbnext/2.3/ruby-next/language/rewriters/3.1/anonymous_block.rb
67
68
  - lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb
@@ -170,7 +171,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
170
171
  - !ruby/object:Gem::Version
171
172
  version: '0'
172
173
  requirements: []
173
- rubygems_version: 3.3.7
174
+ rubygems_version: 3.3.11
174
175
  signing_key:
175
176
  specification_version: 4
176
177
  summary: Ruby Next core functionality