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
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ class RightHandAssignment < Base
7
+ NAME = "right-hand-assignment"
8
+ SYNTAX_PROBE = "1 + 2 => a"
9
+ MIN_SUPPORTED_VERSION = Gem::Version.new("3.0.0")
10
+
11
+ def on_rasgn(node)
12
+ context.track! self
13
+
14
+ node = super(node)
15
+
16
+ val_node, asgn_node = *node
17
+
18
+ remove(val_node.loc.expression.end.join(asgn_node.loc.expression))
19
+ insert_before(val_node.loc.expression, "#{asgn_node.loc.expression.source} = ")
20
+
21
+ asgn_node.updated(
22
+ nil,
23
+ asgn_node.children + [val_node]
24
+ )
25
+ end
26
+
27
+ def on_vasgn(node)
28
+ return super(node) unless rightward?(node)
29
+
30
+ context.track! self
31
+
32
+ name, val_node = *node
33
+
34
+ remove(val_node.loc.expression.end.join(node.loc.name))
35
+ insert_before(val_node.loc.expression, "#{name} = ")
36
+
37
+ super(node)
38
+ end
39
+
40
+ def on_casgn(node)
41
+ return super(node) unless rightward?(node)
42
+
43
+ context.track! self
44
+
45
+ scope_node, name, val_node = *node
46
+
47
+ if scope_node
48
+ scope = scope_node.type == :cbase ? scope_node.loc.expression.source : "#{scope_node.loc.expression.source}::"
49
+ name = "#{scope}#{name}"
50
+ end
51
+
52
+ remove(val_node.loc.expression.end.join(node.loc.name))
53
+ insert_before(val_node.loc.expression, "#{name} = ")
54
+
55
+ super(node)
56
+ end
57
+
58
+ def on_mrasgn(node)
59
+ context.track! self
60
+
61
+ node = super(node)
62
+
63
+ lhs, rhs = *node
64
+
65
+ replace(lhs.loc.expression.end.join(rhs.loc.expression), ")")
66
+ insert_before(lhs.loc.expression, "#{rhs.loc.expression.source} = (")
67
+
68
+ node.updated(
69
+ :masgn,
70
+ [rhs, lhs]
71
+ )
72
+ end
73
+
74
+ def on_masgn(node)
75
+ return super(node) unless rightward?(node)
76
+
77
+ context.track! self
78
+
79
+ rhs, lhs = *node
80
+
81
+ replace(lhs.loc.expression.end.join(rhs.loc.expression), ")")
82
+ insert_before(lhs.loc.expression, "#{rhs.loc.expression.source} = (")
83
+
84
+ super(node)
85
+ end
86
+
87
+ private
88
+
89
+ def rightward?(node)
90
+ # Location could be empty for node built by rewriters
91
+ return false unless ((!node.loc.nil?) || nil) && node.loc.operator
92
+
93
+ assignee_loc =
94
+ if node.type == :masgn
95
+ node.children[0].loc.expression
96
+ else
97
+ node.loc.name
98
+ end
99
+
100
+ return false unless assignee_loc
101
+
102
+ assignee_loc.begin_pos > node.loc.operator.end_pos
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Utils
5
+ module_function
6
+
7
+ if $LOAD_PATH.respond_to?(:resolve_feature_path)
8
+ def resolve_feature_path(feature)
9
+ ((!$LOAD_PATH.resolve_feature_path(feature).nil?) || nil) && $LOAD_PATH.resolve_feature_path(feature).last
10
+ rescue LoadError
11
+ end
12
+ else
13
+ def resolve_feature_path(path)
14
+ if File.file?(relative = File.expand_path(path))
15
+ path = relative
16
+ end
17
+
18
+ path = "#{path}.rb" if File.extname(path).empty?
19
+
20
+ return path if Pathname.new(path).absolute?
21
+
22
+ $LOAD_PATH.find do |lp|
23
+ lpath = File.join(lp, path)
24
+ return File.realpath(lpath) if File.file?(lpath)
25
+ end
26
+ end
27
+ end
28
+
29
+ def source_with_lines(source, path)
30
+ source.lines.map.with_index do |line, i|
31
+ "#{(i + 1).to_s.rjust(4)}: #{line}"
32
+ end.tap do |lines|
33
+ lines.unshift " 0: # source: #{path}"
34
+ end
35
+ end
36
+
37
+ # Returns true if modules refinement is supported in current version
38
+ def refine_modules?
39
+ @refine_modules ||=
40
+ begin
41
+ # Make sure that including modules within refinements works
42
+ # See https://github.com/oracle/truffleruby/issues/2026
43
+ eval <<-RUBY, TOPLEVEL_BINDING, __FILE__, __LINE__ + 1
44
+ module RubyNext::Utils::A; end
45
+ class RubyNext::Utils::B
46
+ include RubyNext::Utils::A
47
+ end
48
+ using(Module.new do
49
+ refine RubyNext::Utils::A do
50
+ include(Module.new do
51
+ def i_am_refinement
52
+ "yes, you are!"
53
+ end
54
+ end)
55
+ end
56
+ end)
57
+ RubyNext::Utils::B.new.i_am_refinement
58
+ RUBY
59
+ true
60
+ rescue TypeError, NoMethodError
61
+ false
62
+ end
63
+ end
64
+ end
65
+ end
@@ -4,17 +4,18 @@ require "ruby-next/version"
4
4
 
5
5
  module RubyNext
6
6
  # Mininum Ruby version supported by RubyNext
7
- MIN_SUPPORTED_VERSION = Gem::Version.new("2.5.0")
7
+ MIN_SUPPORTED_VERSION = Gem::Version.new("2.2.0")
8
8
 
9
9
  # Where to store transpiled files (relative from the project LOAD_PATH, usually `lib/`)
10
10
  RUBY_NEXT_DIR = ".rbnext"
11
11
 
12
12
  # Defines last minor version for every major version
13
13
  LAST_MINOR_VERSIONS = {
14
- 2 => 8
14
+ 2 => 8, # 2.8 is required for backward compatibility: some gems already uses it
15
+ 3 => 0
15
16
  }.freeze
16
17
 
17
- LATEST_VERSION = [2, 8].freeze
18
+ LATEST_VERSION = [3, 0].freeze
18
19
 
19
20
  class << self
20
21
  def next_version(version = RUBY_VERSION)
@@ -33,7 +34,8 @@ module RubyNext
33
34
  end
34
35
  end
35
36
 
36
- require_relative "ruby-next/core"
37
- require_relative "ruby-next/core_ext" if RubyNext::Core.core_ext?
38
- require_relative "ruby-next/logging"
37
+ require "ruby-next/setup_self"
38
+ require "ruby-next/core"
39
+ require "ruby-next/core_ext" if RubyNext::Core.core_ext?
40
+ require "ruby-next/logging"
39
41
  end
@@ -61,7 +61,7 @@ module RubyNext
61
61
  def maybe_print_help
62
62
  return unless @print_help
63
63
 
64
- STDOUT.puts optparser.help
64
+ $stdout.puts optparser.help
65
65
  exit 0
66
66
  end
67
67
 
@@ -85,7 +85,7 @@ module RubyNext
85
85
  opts.banner = "Usage: ruby-next COMMAND [options]"
86
86
 
87
87
  opts.on("-v", "--version", "Print version") do
88
- STDOUT.puts RubyNext::VERSION
88
+ $stdout.puts RubyNext::VERSION
89
89
  exit 0
90
90
  end
91
91
 
@@ -160,7 +160,7 @@ module RubyNext
160
160
  # remove empty lines
161
161
  new_src.gsub!(/^\s+$/, "")
162
162
  # remove traling blank lines
163
- new_src.gsub!(/\n\z/, "")
163
+ new_src.delete_suffix!("\n")
164
164
  new_src
165
165
  end
166
166
  end
@@ -10,7 +10,7 @@ module RubyNext
10
10
  class Nextify < Base
11
11
  using RubyNext
12
12
 
13
- attr_reader :lib_path, :paths, :out_path, :min_version, :single_version
13
+ attr_reader :lib_path, :paths, :out_path, :min_version, :single_version, :specified_rewriters
14
14
 
15
15
  def run
16
16
  log "RubyNext core strategy: #{RubyNext::Core.strategy}"
@@ -18,6 +18,8 @@ module RubyNext
18
18
 
19
19
  remove_rbnext!
20
20
 
21
+ @min_version ||= MIN_SUPPORTED_VERSION
22
+
21
23
  paths.each do |path|
22
24
  contents = File.read(path)
23
25
  transpile path, contents
@@ -26,7 +28,8 @@ module RubyNext
26
28
 
27
29
  def parse!(args)
28
30
  print_help = false
29
- @min_version = MIN_SUPPORTED_VERSION
31
+ print_rewriters = false
32
+ rewriter_names = []
30
33
  @single_version = false
31
34
 
32
35
  optparser = base_parser do |opts|
@@ -63,6 +66,14 @@ module RubyNext
63
66
  Core.strategy = :core_ext unless val
64
67
  end
65
68
 
69
+ opts.on("--list-rewriters", "List available rewriters") do |val|
70
+ print_rewriters = true
71
+ end
72
+
73
+ opts.on("--rewrite=REWRITERS...", "Specify particular Ruby features to rewrite") do |val|
74
+ rewriter_names << val
75
+ end
76
+
66
77
  opts.on("-h", "--help", "Print help") do
67
78
  print_help = true
68
79
  end
@@ -77,12 +88,35 @@ module RubyNext
77
88
  exit 0
78
89
  end
79
90
 
91
+ if print_rewriters
92
+ Language.rewriters.each do |rewriter|
93
+ $stdout.puts "#{rewriter::NAME} (\"#{rewriter::SYNTAX_PROBE}\")"
94
+ end
95
+ exit 0
96
+ end
97
+
80
98
  unless lib_path&.then(&File.method(:exist?))
81
99
  $stdout.puts "Path not found: #{lib_path}"
82
100
  $stdout.puts optparser.help
83
101
  exit 2
84
102
  end
85
103
 
104
+ if rewriter_names.any? && min_version
105
+ $stdout.puts "--rewrite cannot be used with --min-version simultaneously"
106
+ exit 2
107
+ end
108
+
109
+ @specified_rewriters =
110
+ if rewriter_names.any?
111
+ begin
112
+ Language.select_rewriters(*rewriter_names)
113
+ rescue Language::RewriterNotFoundError => error
114
+ $stdout.puts error.message
115
+ $stdout.puts "Try --list-rewriters to see list of available rewriters"
116
+ exit 2
117
+ end
118
+ end
119
+
86
120
  @paths =
87
121
  if File.directory?(lib_path)
88
122
  Dir[File.join(lib_path, "**/*.rb")]
@@ -96,16 +130,11 @@ module RubyNext
96
130
  private
97
131
 
98
132
  def transpile(path, contents, version: min_version)
99
- rewriters = Language.rewriters.select { |rw| rw.unsupported_version?(version) }
133
+ rewriters = specified_rewriters || Language.rewriters.select { |rw| rw.unsupported_version?(version) }
100
134
 
101
135
  context = Language::TransformContext.new
102
136
 
103
- new_contents =
104
- if Gem::Version.new(version) >= Gem::Version.new("2.7.0") && !defined?(Unparser::Emitter::CaseMatch)
105
- Language.rewrite contents, context: context, rewriters: rewriters
106
- else
107
- Language.transform contents, context: context, rewriters: rewriters
108
- end
137
+ new_contents = Language.transform contents, context: context, rewriters: rewriters
109
138
 
110
139
  return unless context.dirty?
111
140
 
@@ -161,7 +190,9 @@ module RubyNext
161
190
  out_path == "stdout"
162
191
  end
163
192
 
164
- alias single_version? single_version
193
+ def single_version?
194
+ single_version || specified_rewriters
195
+ end
165
196
  end
166
197
  end
167
198
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "set"
4
4
 
5
- require_relative "utils"
5
+ require "ruby-next/utils"
6
6
 
7
7
  module RubyNext
8
8
  module Core
@@ -25,7 +25,7 @@ module RubyNext
25
25
  @supported = supported.nil? ? mod.method_defined?(method_name) : supported
26
26
  # define whether running Ruby has a native implementation for this method
27
27
  # for that, we check the source_location (which is nil for C defined methods)
28
- @native = native.nil? ? (supported? && mod.instance_method(method_name).source_location.nil?) : native
28
+ @native = native.nil? ? (supported? && native_location?(mod.instance_method(method_name).source_location)) : native
29
29
  end
30
30
  @singleton = singleton
31
31
  @refineables = Array(refineable)
@@ -75,6 +75,10 @@ module RubyNext
75
75
 
76
76
  [trace_location.absolute_path, trace_location.lineno + 2]
77
77
  end
78
+
79
+ def native_location?(location)
80
+ location.nil? || location.first.match?(/(<internal:|resource:\/truffleruby\/core)/)
81
+ end
78
82
  end
79
83
 
80
84
  # Registry for patches
@@ -144,29 +148,29 @@ end
144
148
 
145
149
  require "backports/2.5" if RubyNext::Core.backports?
146
150
 
147
- require_relative "core/kernel/then"
151
+ require "ruby-next/core/kernel/then"
148
152
 
149
- require_relative "core/proc/compose"
153
+ require "ruby-next/core/proc/compose"
150
154
 
151
- require_relative "core/enumerable/tally"
152
- require_relative "core/enumerable/filter"
153
- require_relative "core/enumerable/filter_map"
155
+ require "ruby-next/core/enumerable/tally"
156
+ require "ruby-next/core/enumerable/filter"
157
+ require "ruby-next/core/enumerable/filter_map"
154
158
 
155
- require_relative "core/enumerator/produce"
159
+ require "ruby-next/core/enumerator/produce"
156
160
 
157
- require_relative "core/array/difference_union_intersection"
161
+ require "ruby-next/core/array/difference_union_intersection"
158
162
 
159
- require_relative "core/hash/merge"
163
+ require "ruby-next/core/hash/merge"
160
164
 
161
- require_relative "core/string/split"
165
+ require "ruby-next/core/string/split"
162
166
 
163
- require_relative "core/symbol/start_with"
164
- require_relative "core/symbol/end_with"
167
+ require "ruby-next/core/symbol/start_with"
168
+ require "ruby-next/core/symbol/end_with"
165
169
 
166
- require_relative "core/unboundmethod/bind_call"
170
+ require "ruby-next/core/unboundmethod/bind_call"
167
171
 
168
- require_relative "core/time/floor"
169
- require_relative "core/time/ceil"
172
+ require "ruby-next/core/time/floor"
173
+ require "ruby-next/core/time/ceil"
170
174
 
171
175
  # Core extensions required for pattern matching
172
176
  # Required for pattern matching with refinements
@@ -175,11 +179,13 @@ unless defined?(NoMatchingPatternError)
175
179
  end
176
180
  end
177
181
 
178
- require_relative "core/constants/no_matching_pattern_error"
179
- require_relative "core/array/deconstruct"
180
- require_relative "core/hash/deconstruct_keys"
181
- require_relative "core/struct/deconstruct"
182
- require_relative "core/struct/deconstruct_keys"
182
+ require "ruby-next/core/constants/no_matching_pattern_error"
183
+ require "ruby-next/core/array/deconstruct"
184
+ require "ruby-next/core/hash/deconstruct_keys"
185
+ require "ruby-next/core/struct/deconstruct"
186
+ require "ruby-next/core/struct/deconstruct_keys"
187
+
188
+ require "ruby-next/core/hash/except"
183
189
 
184
190
  # Generate refinements
185
191
  RubyNext.module_eval do
@@ -1,21 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  RubyNext::Core.patch Array, method: :deconstruct, version: "2.7" do
4
- <<~RUBY
5
- def deconstruct
6
- self
7
- end
4
+ <<-RUBY
5
+ def deconstruct
6
+ self
7
+ end
8
8
  RUBY
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
12
  if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.6")
13
13
  RubyNext::Core.patch refineable: Array, name: "ArrayRespondToDeconstruct", method: :deconstruct, version: "2.7" do
14
- <<~RUBY
15
- def respond_to?(mid, *)
16
- return true if mid == :deconstruct
17
- super
18
- end
14
+ <<-RUBY
15
+ def respond_to?(mid, *)
16
+ return true if mid == :deconstruct
17
+ super
18
+ end
19
19
  RUBY
20
20
  end
21
21
  end