ruby-next-core 0.10.5 → 0.11.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +9 -14
  4. data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +1 -1
  5. data/lib/.rbnext/2.3/ruby-next/language/eval.rb +3 -3
  6. data/lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb +19 -1
  7. data/lib/.rbnext/2.3/ruby-next/language/rewriters/pattern_matching.rb +17 -14
  8. data/lib/.rbnext/2.7/ruby-next/core.rb +203 -0
  9. data/lib/ruby-next.rb +5 -38
  10. data/lib/ruby-next/commands/nextify.rb +1 -1
  11. data/lib/ruby-next/config.rb +45 -0
  12. data/lib/ruby-next/core.rb +5 -4
  13. data/lib/ruby-next/core/array/deconstruct.rb +1 -1
  14. data/lib/ruby-next/core/constants/no_matching_pattern_error.rb +1 -1
  15. data/lib/ruby-next/core/hash/deconstruct_keys.rb +1 -1
  16. data/lib/ruby-next/core/struct/deconstruct_keys.rb +3 -3
  17. data/lib/ruby-next/core_ext.rb +2 -0
  18. data/lib/ruby-next/language.rb +15 -4
  19. data/lib/ruby-next/language/bootsnap.rb +1 -1
  20. data/lib/ruby-next/language/edge.rb +0 -6
  21. data/lib/ruby-next/language/eval.rb +3 -3
  22. data/lib/ruby-next/language/parser.rb +19 -0
  23. data/lib/ruby-next/language/rewriters/args_forward.rb +8 -4
  24. data/lib/ruby-next/language/rewriters/args_forward_leading.rb +75 -0
  25. data/lib/ruby-next/language/rewriters/base.rb +19 -1
  26. data/lib/ruby-next/language/rewriters/in_pattern.rb +56 -0
  27. data/lib/ruby-next/language/rewriters/pattern_matching.rb +17 -14
  28. data/lib/ruby-next/language/setup.rb +6 -3
  29. data/lib/ruby-next/language/unparser.rb +10 -0
  30. data/lib/ruby-next/rubocop.rb +9 -18
  31. data/lib/ruby-next/setup_self.rb +2 -2
  32. data/lib/ruby-next/version.rb +1 -1
  33. metadata +8 -6
  34. data/lib/.rbnext/2.3/ruby-next/language/rewriters/right_hand_assignment.rb +0 -117
  35. data/lib/ruby-next/language/rewriters/right_hand_assignment.rb +0 -117
@@ -57,7 +57,7 @@ module RubyNext
57
57
 
58
58
  opts.on(
59
59
  "--transpile-mode=MODE",
60
- "Transpiler mode (ast or rewrite). Default: ast"
60
+ "Transpiler mode (ast or rewrite). Default: rewrite"
61
61
  ) do |val|
62
62
  Language.mode = val.to_sym
63
63
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ # Mininum Ruby version supported by RubyNext
5
+ MIN_SUPPORTED_VERSION = Gem::Version.new("2.2.0")
6
+
7
+ # Where to store transpiled files (relative from the project LOAD_PATH, usually `lib/`)
8
+ RUBY_NEXT_DIR = ".rbnext"
9
+
10
+ # Defines last minor version for every major version
11
+ LAST_MINOR_VERSIONS = {
12
+ 2 => 8, # 2.8 is required for backward compatibility: some gems already uses it
13
+ 3 => 0
14
+ }.freeze
15
+
16
+ LATEST_VERSION = [3, 0].freeze
17
+
18
+ class << self
19
+ # TruffleRuby claims it's 2.7.2 compatible but...
20
+ if defined?(TruffleRuby) && ::RUBY_VERSION =~ /^2\.7/
21
+ def current_ruby_version
22
+ "2.6.5"
23
+ end
24
+ else
25
+ def current_ruby_version
26
+ ::RUBY_VERSION
27
+ end
28
+ end
29
+
30
+ def next_ruby_version(version = current_ruby_version)
31
+ major, minor = Gem::Version.new(version).segments.map(&:to_i)
32
+
33
+ return if major >= LATEST_VERSION.first && minor >= LATEST_VERSION.last
34
+
35
+ nxt =
36
+ if LAST_MINOR_VERSIONS[major] == minor
37
+ "#{major + 1}.0.0"
38
+ else
39
+ "#{major}.#{minor + 1}.0"
40
+ end
41
+
42
+ Gem::Version.new(nxt)
43
+ end
44
+ end
45
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "set"
4
4
 
5
+ require "ruby-next/config"
5
6
  require "ruby-next/utils"
6
7
 
7
8
  module RubyNext
@@ -17,7 +18,7 @@ module RubyNext
17
18
  # `core_ext` defines the strategy for core extensions:
18
19
  # - :patch — extend class directly
19
20
  # - :prepend — extend class by prepending a module (e.g., when needs `super`)
20
- def initialize(mod = nil, method:, name: nil, version:, supported: nil, native: nil, location: nil, refineable: mod, core_ext: :patch, singleton: nil)
21
+ def initialize(mod = nil, method:, version:, name: nil, supported: nil, native: nil, location: nil, refineable: mod, core_ext: :patch, singleton: nil)
21
22
  @mod = mod
22
23
  @method_name = method
23
24
  @version = version
@@ -122,8 +123,8 @@ module RubyNext
122
123
  strategy == :backports
123
124
  end
124
125
 
125
- def patch(*args, **kwargs, &block)
126
- patches << Patch.new(*args, **kwargs, &block)
126
+ def patch(...)
127
+ patches << Patch.new(...)
127
128
  end
128
129
 
129
130
  # Inject `using RubyNext` at the top of the source code
@@ -175,7 +176,7 @@ require "ruby-next/core/time/ceil"
175
176
  # Core extensions required for pattern matching
176
177
  # Required for pattern matching with refinements
177
178
  unless defined?(NoMatchingPatternError)
178
- class NoMatchingPatternError < RuntimeError
179
+ class NoMatchingPatternError < StandardError
179
180
  end
180
181
  end
181
182
 
@@ -9,7 +9,7 @@ end
9
9
  end
10
10
 
11
11
  # We need to hack `respond_to?` in Ruby 2.5, since it's not working with refinements
12
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.6")
12
+ if Gem::Version.new(::RubyNext.current_ruby_version) < Gem::Version.new("2.6")
13
13
  RubyNext::Core.patch refineable: Array, name: "ArrayRespondToDeconstruct", method: :deconstruct, version: "2.7" do
14
14
  <<-RUBY
15
15
  def respond_to?(mid, *)
@@ -11,7 +11,7 @@ RubyNext::Core.patch Object,
11
11
  supported: true,
12
12
  location: [__FILE__, __LINE__ + 2] do
13
13
  <<-RUBY
14
- class NoMatchingPatternError < RuntimeError
14
+ class NoMatchingPatternError < StandardError
15
15
  end
16
16
  RUBY
17
17
  end
@@ -9,7 +9,7 @@ end
9
9
  end
10
10
 
11
11
  # We need to hack `respond_to?` in Ruby 2.5, since it's not working with refinements
12
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.6")
12
+ if Gem::Version.new(::RubyNext.current_ruby_version) < Gem::Version.new("2.6")
13
13
  RubyNext::Core.patch refineable: Hash, name: "HashRespondToDeconstructKeys", method: :deconstruct_keys, version: "2.7" do
14
14
  <<-RUBY
15
15
  def respond_to?(mid, *)
@@ -10,9 +10,9 @@ def deconstruct_keys(keys)
10
10
 
11
11
  keys.each_with_object({}) do |k, acc|
12
12
  # if k is Symbol and not a member of a Struct return {}
13
- next if (Symbol === k || String === k) && !members.include?(k.to_sym)
13
+ return {} if (Symbol === k || String === k) && !members.include?(k.to_sym)
14
14
  # if k is Integer check that index is not ouf of bounds
15
- next if Integer === k && k > size - 1
15
+ return {} if Integer === k && k > size - 1
16
16
  acc[k] = self[k]
17
17
  end
18
18
  end
@@ -20,7 +20,7 @@ end
20
20
  end
21
21
 
22
22
  # We need to hack `respond_to?` in Ruby 2.5, since it's not working with refinements
23
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.6")
23
+ if Gem::Version.new(::RubyNext.current_ruby_version) < Gem::Version.new("2.6")
24
24
  RubyNext::Core.patch refineable: Struct, name: "StructRespondToDeconstruct", method: :deconstruct_keys, version: "2.7" do
25
25
  <<-RUBY
26
26
  def respond_to?(mid, *)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "ruby-next/config"
4
+ require "ruby-next/setup_self"
3
5
  require "ruby-next/core"
4
6
 
5
7
  # Monkey-patch core classes using the same patches as for refinements
@@ -101,9 +101,9 @@ module RubyNext
101
101
  regenerate(*args, **kwargs)
102
102
  end
103
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"
104
+ if Gem::Version.new(::RubyNext.current_ruby_version) >= Gem::Version.new("3.0.0")
105
+ RubyNext.warn "Ruby Next fallbacks to \"rewrite\" transpiling mode since Unparser doesn't support 3.0+ AST yet.\n" \
106
+ "See https://github.com/mbj/unparser/issues/168"
107
107
  self.mode = :rewrite
108
108
  end
109
109
  rewrite(*args, **kwargs)
@@ -169,7 +169,7 @@ module RubyNext
169
169
 
170
170
  self.rewriters = []
171
171
  self.watch_dirs = %w[app lib spec test].map { |path| File.join(Dir.pwd, path) }
172
- self.mode = ENV.fetch("RUBY_NEXT_TRANSPILE_MODE", "ast").to_sym
172
+ self.mode = ENV.fetch("RUBY_NEXT_TRANSPILE_MODE", "rewrite").to_sym
173
173
 
174
174
  require "ruby-next/language/rewriters/base"
175
175
 
@@ -182,6 +182,11 @@ module RubyNext
182
182
  require "ruby-next/language/rewriters/args_forward"
183
183
  rewriters << Rewriters::ArgsForward
184
184
 
185
+ # Must be added after general args forward rewriter to become
186
+ # no-op in Ruby <2.7
187
+ require "ruby-next/language/rewriters/args_forward_leading"
188
+ rewriters << Rewriters::ArgsForwardLeading
189
+
185
190
  require "ruby-next/language/rewriters/numbered_params"
186
191
  rewriters << Rewriters::NumberedParams
187
192
 
@@ -193,11 +198,17 @@ module RubyNext
193
198
  require "ruby-next/language/rewriters/find_pattern"
194
199
  rewriters << Rewriters::FindPattern
195
200
 
201
+ require "ruby-next/language/rewriters/in_pattern"
202
+ rewriters << Rewriters::InPattern
203
+
196
204
  # Put endless range in the end, 'cause Parser fails to parse it in
197
205
  # pattern matching
198
206
  require "ruby-next/language/rewriters/endless_range"
199
207
  rewriters << Rewriters::EndlessRange
200
208
 
209
+ require "ruby-next/language/rewriters/endless_method"
210
+ RubyNext::Language.rewriters << RubyNext::Language::Rewriters::EndlessMethod
211
+
201
212
  if ENV["RUBY_NEXT_EDGE"] == "1"
202
213
  require "ruby-next/language/edge"
203
214
  end
@@ -13,7 +13,7 @@ load_iseq = RubyVM::InstructionSequence.method(:load_iseq)
13
13
  if load_iseq.source_location[0].include?("/bootsnap/")
14
14
  Bootsnap::CompileCache::ISeq.singleton_class.prepend(
15
15
  Module.new do
16
- def input_to_storage(source, path)
16
+ def input_to_storage(source, path, *)
17
17
  return super unless RubyNext::Language.transformable?(path)
18
18
  source = RubyNext::Language.transform(source, rewriters: RubyNext::Language.current_rewriters)
19
19
 
@@ -1,9 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Load edge Ruby features
4
-
5
- require "ruby-next/language/rewriters/endless_method"
6
- RubyNext::Language.rewriters << RubyNext::Language::Rewriters::EndlessMethod
7
-
8
- require "ruby-next/language/rewriters/right_hand_assignment"
9
- RubyNext::Language.rewriters << RubyNext::Language::Rewriters::RightHandAssignment
@@ -20,7 +20,7 @@ module RubyNext
20
20
  module InstanceEval # :nodoc:
21
21
  refine Object do
22
22
  def instance_eval(*args, &block)
23
- return super(*args, &block) if block_given?
23
+ return super(*args, &block) if block
24
24
 
25
25
  source = args.shift
26
26
  new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
@@ -33,7 +33,7 @@ module RubyNext
33
33
  module ClassEval
34
34
  refine Module do
35
35
  def module_eval(*args, &block)
36
- return super(*args, &block) if block_given?
36
+ return super(*args, &block) if block
37
37
 
38
38
  source = args.shift
39
39
  new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
@@ -42,7 +42,7 @@ module RubyNext
42
42
  end
43
43
 
44
44
  def class_eval(*args, &block)
45
- return super(*args, &block) if block_given?
45
+ return super(*args, &block) if block
46
46
 
47
47
  source = args.shift
48
48
  new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
@@ -4,8 +4,27 @@ require "parser/rubynext"
4
4
 
5
5
  module RubyNext
6
6
  module Language
7
+ module BuilderExt
8
+ def match_pattern(lhs, match_t, rhs)
9
+ n(:match_pattern, [lhs, rhs],
10
+ binary_op_map(lhs, match_t, rhs))
11
+ end
12
+
13
+ def match_pattern_p(lhs, match_t, rhs)
14
+ n(:match_pattern_p, [lhs, rhs],
15
+ binary_op_map(lhs, match_t, rhs))
16
+ end
17
+ end
18
+
7
19
  class Builder < ::Parser::Builders::Default
8
20
  modernize
21
+
22
+ # Unparser doesn't support kwargs node yet
23
+ self.emit_kwargs = false if respond_to?(:emit_kwargs=)
24
+
25
+ unless method_defined?(:match_pattern_p)
26
+ include BuilderExt
27
+ end
9
28
  end
10
29
 
11
30
  class << self
@@ -5,8 +5,8 @@ module RubyNext
5
5
  module Rewriters
6
6
  class ArgsForward < Base
7
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")
8
+ SYNTAX_PROBE = "obj = Object.new; def obj.foo(...) super(...); end"
9
+ MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
10
10
 
11
11
  REST = :__rest__
12
12
  BLOCK = :__block__
@@ -28,14 +28,14 @@ module RubyNext
28
28
  end
29
29
 
30
30
  def on_send(node)
31
- fargs = node.children.find { |child| child.is_a?(::Parser::AST::Node) && child.type == :forwarded_args }
31
+ fargs = extract_fargs(node)
32
32
  return super(node) unless fargs
33
33
 
34
34
  process_fargs(node, fargs)
35
35
  end
36
36
 
37
37
  def on_super(node)
38
- fargs = node.children.find { |child| child.is_a?(::Parser::AST::Node) && child.type == :forwarded_args }
38
+ fargs = extract_fargs(node)
39
39
  return super(node) unless fargs
40
40
 
41
41
  process_fargs(node, fargs)
@@ -43,6 +43,10 @@ module RubyNext
43
43
 
44
44
  private
45
45
 
46
+ def extract_fargs(node)
47
+ node.children.find { |child| child.is_a?(::Parser::AST::Node) && child.type == :forwarded_args }
48
+ end
49
+
46
50
  def process_fargs(node, fargs)
47
51
  replace(fargs.loc.expression, "*#{REST}, &#{BLOCK}")
48
52
 
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ class ArgsForwardLeading < ArgsForward
7
+ NAME = "args-forward-leading"
8
+ SYNTAX_PROBE = "obj = Object.new; def obj.foo(...) super(1, ...); end"
9
+ MIN_SUPPORTED_VERSION = Gem::Version.new("3.0.0")
10
+
11
+ attr_reader :leading_farg
12
+ alias leading_farg? leading_farg
13
+
14
+ def on_def(node)
15
+ @leading_farg = method_with_leading_arg(node)
16
+
17
+ super
18
+ end
19
+
20
+ def on_defs(node)
21
+ @leading_farg = method_with_leading_arg(node)
22
+
23
+ super
24
+ end
25
+
26
+ def on_forward_arg(node)
27
+ return super if leading_farg?
28
+
29
+ node
30
+ end
31
+
32
+ def on_send(node)
33
+ return super if leading_farg?
34
+
35
+ node
36
+ end
37
+
38
+ def on_super(node)
39
+ return super if leading_farg?
40
+
41
+ node
42
+ end
43
+
44
+ private
45
+
46
+ def send_with_leading_farg(node)
47
+ return false unless node.type == :send || node.type == :super
48
+
49
+ fargs = extract_fargs(node)
50
+
51
+ return false unless fargs
52
+
53
+ node.children.index(fargs) > (node.type == :send ? 2 : 0)
54
+ end
55
+
56
+ def method_with_leading_arg(node)
57
+ find_child(node) { |child| child.type == :forward_arg } &&
58
+ (
59
+ def_with_leading_farg(node) ||
60
+ find_child(node) { |child| send_with_leading_farg(child) }
61
+ )
62
+ end
63
+
64
+ def def_with_leading_farg(node)
65
+ args = node.type == :defs ? node.children[2] : node.children[1]
66
+ args = args.children
67
+
68
+ farg = args.detect { |child| child.type == :forward_arg }
69
+
70
+ args.index(farg) > 0
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -61,7 +61,7 @@ module RubyNext
61
61
  eval_mid = Kernel.respond_to?(:eval_without_ruby_next) ? :eval_without_ruby_next : :eval
62
62
  Kernel.send eval_mid, self::SYNTAX_PROBE, nil, __FILE__, __LINE__
63
63
  false
64
- rescue SyntaxError, NameError
64
+ rescue SyntaxError, StandardError
65
65
  true
66
66
  ensure
67
67
  $VERBOSE = save_verbose
@@ -94,6 +94,24 @@ module RubyNext
94
94
 
95
95
  private
96
96
 
97
+ # BFS with predicate block
98
+ def find_child(node)
99
+ queue = [node]
100
+
101
+ loop do
102
+ break if queue.empty?
103
+
104
+ child = queue.shift
105
+ next unless child.is_a?(::Parser::AST::Node)
106
+
107
+ return child if yield child
108
+
109
+ queue.push(*child.children)
110
+ end
111
+
112
+ nil
113
+ end
114
+
97
115
  def replace(range, ast)
98
116
  @source_rewriter&.replace(range, unparse(ast))
99
117
  end
@@ -0,0 +1,56 @@
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 `in` patterns
12
+ class InPattern < PatternMatching
13
+ NAME = "pattern-matching-in"
14
+ SYNTAX_PROBE = "1 in 2"
15
+ MIN_SUPPORTED_VERSION = Gem::Version.new("3.0.0")
16
+
17
+ # Make case-match no-op
18
+ def on_case_match(node)
19
+ node
20
+ end
21
+
22
+ def on_match_pattern_p(node)
23
+ context.track! self
24
+
25
+ @deconstructed_keys = {}
26
+ @predicates = Predicates::Noop.new
27
+
28
+ matchee =
29
+ s(:begin, s(:lvasgn, MATCHEE, node.children[0]))
30
+
31
+ pattern =
32
+ locals.with(
33
+ matchee: MATCHEE,
34
+ arr: MATCHEE_ARR,
35
+ hash: MATCHEE_HASH
36
+ ) do
37
+ send(
38
+ :"#{node.children[1].type}_clause",
39
+ node.children[1]
40
+ )
41
+ end
42
+
43
+ node.updated(
44
+ :and,
45
+ [
46
+ matchee,
47
+ pattern
48
+ ]
49
+ ).tap do |new_node|
50
+ replace(node.loc.expression, inline_blocks(unparse(new_node)))
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end