ruby-next-core 0.10.5 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34691e69e3f0725ece379addac8297e0b69e9ed0a55fb1a06bfe3ca970213eae
4
- data.tar.gz: 23edfba446641d2658d754f3678eb79de8ee332a4d481d4714887769392ad272
3
+ metadata.gz: 37dbd285f7d0d450d8458a2dc5c01e7b8c5f7744e54ea05d133ea7444dc9704c
4
+ data.tar.gz: 7a881b5d76d032ce4ee5a94515542bff75a3a5b899b8412d828d9c4a27728eda
5
5
  SHA512:
6
- metadata.gz: 0d41912b15fd77ed3a1d2d2665a3375a212a1815e26e85724d2e0960af1daa5c714b126efca1c5621dc76b16af3d612c35a077be3472ca607548a757dea253ac
7
- data.tar.gz: 02d860b6d0730f48b990c6c8dd9a3ef49e022ea7a0ae637a64cd0c29a74ee1e6c2b0f4cdf0ed25886029a49d9e2df8044b9e016aead9733ca322fd3579093949
6
+ metadata.gz: '0971fe638bfbd8c1ac3e5aa5485457fd9e9e70cfbb6f6430222d18ae13ce1820e97f3e28f93393d8c4ecc5578df0113d229b0179f5f07a2e1e19ae94087112ff'
7
+ data.tar.gz: aefa941c90244ae1e7c68eb7bae6f79c2fb936495e28158f8eb18a8b90dc8eb7dcbf35a50d133b8b1653a0bbb118469c989ed97cb6e7039d383c6264aca2f6bd
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.11.0 (2020-12-24) 🎄
6
+
7
+ - Extended proposed shorthand Hash syntax to support kwargs as well. ([@palkan][])
8
+
9
+ You can try it: `x = 1; y = 2; foo(x:, y:) # => foo(x: x, y: y)`.
10
+
11
+ - **Rewrite mode is used by default in transpiler**. ([@palkan][])
12
+
13
+ - Move 3.0 features to stable features. ([@palkan][])
14
+
15
+ - Refactor `a => x` and `a in x` to comply with Ruby 3.0. ([@palkan][])
16
+
5
17
  ## 0.10.5 (2020-10-13)
6
18
 
7
19
  - Fix Unparser 0.5.0 compatibility. ([@palkan][])
data/README.md CHANGED
@@ -93,8 +93,7 @@ def greet(val) =
93
93
  '👽'
94
94
  end
95
95
 
96
- greet(hello: 'martian') => greeting
97
- puts greeting
96
+ puts greet(hello: 'martian')
98
97
  "
99
98
 
100
99
  => 👽
@@ -179,17 +178,17 @@ In the AST mode, we parse the source code into AST, modifies this AST and **gene
179
178
 
180
179
  In the rewrite mode, we apply changes to the source code itself, thus, keeping the original formatting of the unaffected code (in a similar way to RuboCop's autocorrect feature).
181
180
 
182
- By default, we use the AST mode. That could likely change in the future when we collect enough feedback on the rewrite mode and fix potential bugs.
183
-
184
181
  The main benefit of the rewrite mode is that it preserves the original code line numbers and layout, which is especially useful in debugging.
185
182
 
183
+ By default, we use the rewrite mode. If you found a bug with rewrite mode which is not reproducible in the AST mode, please, let us know.
184
+
186
185
  You can change the transpiler mode:
187
186
 
188
187
  - From code by setting `RubyNext::Language.mode = :ast` or `RubyNext::Language.mode = :rewrite`.
189
- - Via environmental variable `RUBY_NEXT_TRANSPILE_MODE=rewrite`.
188
+ - Via environmental variable `RUBY_NEXT_TRANSPILE_MODE=ast`.
190
189
  - Via CLI option ([see below](#cli)).
191
190
 
192
- **NOTE:** For the time being, Unparser [doesn't support](https://github.com/mbj/unparser/pull/142) new Ruby 2.7 AST nodes, so we always use rewrite mode in Ruby 2.7+.
191
+ **NOTE:** For the time being, Unparser doesn't support Ruby 3.0 AST nodes, so we always use rewrite mode in Ruby 3.0+.
193
192
 
194
193
  ## CLI
195
194
 
@@ -220,7 +219,7 @@ Usage: ruby-next nextify DIRECTORY_OR_FILE [options]
220
219
 
221
220
  The behaviour depends on whether you transpile a single file or a directory:
222
221
 
223
- - When transpiling a directory, the `.rbnext` subfolder is created within the target folder with subfolders for each supported Ruby versions (e.g., `.rbnext/2.6`, `.rbnext/2.7`). If you want to create only a single version (the smallest), you can also pass `--single-version` flag. In that case, no version directory is created (i.e., transpiled files go into `.rbnext`).
222
+ - When transpiling a directory, the `.rbnext` subfolder is created within the target folder with subfolders for each supported Ruby versions (e.g., `.rbnext/2.6`, `.rbnext/2.7`, `.rbnext/3.0`). If you want to create only a single version (the smallest), you can also pass `--single-version` flag. In that case, no version directory is created (i.e., transpiled files go into `.rbnext`).
224
223
 
225
224
  - When transpiling a file and providing the output path as a _file_ path, only a single version is created. For example:
226
225
 
@@ -323,7 +322,7 @@ due to the way feature resolving works in Ruby (scanning the `$LOAD_PATH` and ha
323
322
 
324
323
  If you're using [runtime mode](#runtime-usage) a long with `setup_gem_load_path` (e.g., in tests), the transpiled files are ignored (i.e., we do not modify `$LOAD_PATH`).
325
324
 
326
- \* Ruby Next avoids storing duplicates; instead, only the code for the earlier version is created and is assumed to be used with other versions. For example, if the transpiled code is the same for Ruby 2.5 and Ruby 2.6, only the `.rbnext/2.7/path/to/file.rb` is kept. That's why multiple entries are added to the `$LOAD_PATH` (`.rbnext/2.6` and `.rbnext/2.7` in the specified order for Ruby 2.5 and only `.rbnext/2.7` for Ruby 2.6).
325
+ \* Ruby Next avoids storing duplicates; instead, only the code for the earlier version is created and is assumed to be used with other versions. For example, if the transpiled code is the same for Ruby 2.5 and Ruby 2.6, only the `.rbnext/2.7/path/to/file.rb` is kept. That's why multiple entries are added to the `$LOAD_PATH` (`.rbnext/2.6`, `.rbnext/2.7`, and `.rbnext/3.0` in the specified order for Ruby 2.5, and `.rbnext/2.7` and `.rbnext/3.0` for Ruby 2.6).
327
326
 
328
327
  ### Transpiled files vs. VCS vs. installing from source
329
328
 
@@ -509,17 +508,13 @@ require "ruby-next/language/runtime"
509
508
 
510
509
  ### Supported edge features
511
510
 
512
- - "Endless" method definition (`def foo() = 42`) ([#16746](https://bugs.ruby-lang.org/issues/16746)).
513
-
514
- - Right-hand assignment (`13.divmod(5) => a,b`) ([#15921](https://bugs.ruby-lang.org/issues/15921)).
515
-
516
- - Find pattern (`[0, 1, 2] in [*, 1 => a, *c]`) ([#16828](https://bugs.ruby-lang.org/issues/16828)).
511
+ No new features since 3.0 release.
517
512
 
518
513
  ### Supported proposed features
519
514
 
520
515
  - _Method reference_ operator (`.:`) ([#13581](https://bugs.ruby-lang.org/issues/13581)).
521
516
 
522
- - Shorthand Hash notation (`data = {x, y}`) ([#15236](https://bugs.ruby-lang.org/issues/15236)).
517
+ - Shorthand Hash/kwarg notation (`data = {x, y}` or `foo(x:, y:)`) ([#15236](https://bugs.ruby-lang.org/issues/15236)).
523
518
 
524
519
  ## Contributing
525
520
 
@@ -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
@@ -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)
@@ -60,7 +60,7 @@ module RubyNext
60
60
  eval_mid = Kernel.respond_to?(:eval_without_ruby_next) ? :eval_without_ruby_next : :eval
61
61
  Kernel.send eval_mid, self::SYNTAX_PROBE, nil, __FILE__, __LINE__
62
62
  false
63
- rescue SyntaxError, NameError
63
+ rescue SyntaxError, StandardError
64
64
  true
65
65
  ensure
66
66
  $VERBOSE = save_verbose
@@ -93,6 +93,24 @@ module RubyNext
93
93
 
94
94
  private
95
95
 
96
+ # BFS with predicate block
97
+ def find_child(node)
98
+ queue = [node]
99
+
100
+ loop do
101
+ break if queue.empty?
102
+
103
+ child = queue.shift
104
+ next unless child.is_a?(::Parser::AST::Node)
105
+
106
+ return child if yield child
107
+
108
+ queue.push(*child.children)
109
+ end
110
+
111
+ nil
112
+ end
113
+
96
114
  def replace(range, ast)
97
115
  ((!@source_rewriter.nil? || nil) && @source_rewriter.replace(range, unparse(ast)))
98
116
  end
@@ -13,12 +13,12 @@ module RubyNext
13
13
 
14
14
  # Useful to generate simple operation nodes
15
15
  # (e.g., 'a + b')
16
- def -(val)
17
- ::Parser::AST::Node.new(:send, [self, :-, val.to_ast_node])
16
+ def -(other)
17
+ ::Parser::AST::Node.new(:send, [self, :-, other.to_ast_node])
18
18
  end
19
19
 
20
- def +(val)
21
- ::Parser::AST::Node.new(:send, [self, :+, val.to_ast_node])
20
+ def +(other)
21
+ ::Parser::AST::Node.new(:send, [self, :+, other.to_ast_node])
22
22
  end
23
23
  end
24
24
 
@@ -218,9 +218,10 @@ module RubyNext
218
218
  predicate_clause(:respond_to_deconstruct_keys, node)
219
219
  end
220
220
 
221
- def hash_key(node, key)
222
- key = key.children.first if key.is_a?(::Parser::AST::Node)
223
- predicate_clause(:"hash_key_#{key}", node)
221
+ def hash_keys(node, keys)
222
+ keys = keys.map { |key| key.is_a?(::Parser::AST::Node) ? key.children.first : key }
223
+
224
+ predicate_clause(:"hash_keys_#{keys.join("_p_")}", node)
224
225
  end
225
226
  end
226
227
  end
@@ -266,7 +267,7 @@ module RubyNext
266
267
  )
267
268
  end
268
269
 
269
- def on_in_match(node)
270
+ def on_match_pattern(node)
270
271
  context.track! self
271
272
 
272
273
  @deconstructed_keys = {}
@@ -303,6 +304,8 @@ module RubyNext
303
304
  end
304
305
  end
305
306
 
307
+ alias on_in_match on_match_pattern
308
+
306
309
  private
307
310
 
308
311
  def rewrite_case_in!(node, matchee, new_node)
@@ -883,14 +886,14 @@ module RubyNext
883
886
  end
884
887
 
885
888
  def having_hash_keys(keys, hash = s(:lvar, locals[:hash]))
886
- key = keys.shift
887
- node = predicates.hash_key(hash_has_key(key, hash), key)
889
+ keys.reduce(nil) do |acc, key|
890
+ pnode = hash_has_key(key, hash)
891
+ next pnode unless acc
888
892
 
889
- keys.reduce(node) do |res, key|
890
893
  s(:begin,
891
- s(:and,
892
- res,
893
- predicates.hash_key(hash_has_key(key, hash), key)))
894
+ s(:and, acc, pnode))
895
+ end.then do |node|
896
+ predicates.hash_keys(node, keys)
894
897
  end
895
898
  end
896
899
 
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ require "ruby-next/config"
6
+ require "ruby-next/utils"
7
+
8
+ module RubyNext
9
+ module Core
10
+ # Patch contains the extension implementation
11
+ # and meta information (e.g., Ruby version).
12
+ class Patch
13
+ attr_reader :refineables, :name, :mod, :method_name, :version, :body, :singleton, :core_ext, :supported, :native, :location
14
+
15
+ # Create a new patch for module/class (mod)
16
+ # with the specified uniq name
17
+ #
18
+ # `core_ext` defines the strategy for core extensions:
19
+ # - :patch — extend class directly
20
+ # - :prepend — extend class by prepending a module (e.g., when needs `super`)
21
+ def initialize(mod = nil, method:, version:, name: nil, supported: nil, native: nil, location: nil, refineable: mod, core_ext: :patch, singleton: nil)
22
+ @mod = mod
23
+ @method_name = method
24
+ @version = version
25
+ if method_name && mod
26
+ @supported = supported.nil? ? mod.method_defined?(method_name) : supported
27
+ # define whether running Ruby has a native implementation for this method
28
+ # for that, we check the source_location (which is nil for C defined methods)
29
+ @native = native.nil? ? (supported? && native_location?(mod.instance_method(method_name).source_location)) : native
30
+ end
31
+ @singleton = singleton
32
+ @refineables = Array(refineable)
33
+ @body = yield
34
+ @core_ext = core_ext
35
+ @location = location || build_location(caller_locations(1, 5))
36
+ @name = name || build_module_name
37
+ end
38
+
39
+ def prepend?
40
+ core_ext == :prepend
41
+ end
42
+
43
+ def core_ext?
44
+ !mod.nil?
45
+ end
46
+
47
+ alias supported? supported
48
+ alias native? native
49
+ alias singleton? singleton
50
+
51
+ def to_module
52
+ Module.new.tap do |ext|
53
+ ext.module_eval(body, *location)
54
+
55
+ RubyNext::Core.const_set(name, ext)
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def build_module_name
62
+ mod_name = singleton? ? singleton.name : mod.name
63
+ camelized_method_name = method_name.to_s.split("_").map(&:capitalize).join
64
+
65
+ "#{mod_name}#{camelized_method_name}".gsub(/\W/, "")
66
+ end
67
+
68
+ def build_location(trace_locations)
69
+ # The caller_locations behaviour depends on implementaion,
70
+ # e.g. in JRuby https://github.com/jruby/jruby/issues/6055
71
+ while trace_locations.first.label != "patch"
72
+ trace_locations.shift
73
+ end
74
+
75
+ trace_location = trace_locations[1]
76
+
77
+ [trace_location.absolute_path, trace_location.lineno + 2]
78
+ end
79
+
80
+ def native_location?(location)
81
+ location.nil? || location.first.match?(/(<internal:|resource:\/truffleruby\/core)/)
82
+ end
83
+ end
84
+
85
+ # Registry for patches
86
+ class Patches
87
+ attr_reader :extensions, :refined
88
+
89
+ def initialize
90
+ @names = Set.new
91
+ @extensions = Hash.new { |h, k| h[k] = [] }
92
+ @refined = Hash.new { |h, k| h[k] = [] }
93
+ end
94
+
95
+ # Register new patch
96
+ def <<(patch)
97
+ raise ArgumentError, "Patch already registered: #{patch.name}" if @names.include?(patch.name)
98
+ @names << patch.name
99
+ @extensions[patch.mod] << patch if patch.core_ext?
100
+ patch.refineables.each { |r| @refined[r] << patch } unless patch.native?
101
+ end
102
+ end
103
+
104
+ class << self
105
+ STRATEGIES = %i[refine core_ext backports].freeze
106
+
107
+ attr_reader :strategy
108
+
109
+ def strategy=(val)
110
+ raise ArgumentError, "Unknown strategy: #{val}. Available: #{STRATEGIES.join(",")}" unless STRATEGIES.include?(val)
111
+ @strategy = val
112
+ end
113
+
114
+ def refine?
115
+ strategy == :refine
116
+ end
117
+
118
+ def core_ext?
119
+ strategy == :core_ext || strategy == :backports
120
+ end
121
+
122
+ def backports?
123
+ strategy == :backports
124
+ end
125
+
126
+ def patch(*__rest__, &__block__)
127
+ patches << Patch.new(*__rest__, &__block__)
128
+ end
129
+
130
+ # Inject `using RubyNext` at the top of the source code
131
+ def inject!(contents)
132
+ if contents.frozen?
133
+ contents = contents.sub(/^(\s*[^#\s].*)/, 'using RubyNext;\1')
134
+ else
135
+ contents.sub!(/^(\s*[^#\s].*)/, 'using RubyNext;\1')
136
+ end
137
+ contents
138
+ end
139
+
140
+ def patches
141
+ @patches ||= Patches.new
142
+ end
143
+ end
144
+
145
+ # Use refinements by default
146
+ self.strategy = ENV.fetch("RUBY_NEXT_CORE_STRATEGY", "refine").to_sym
147
+ end
148
+ end
149
+
150
+ require "backports/2.5" if RubyNext::Core.backports?
151
+
152
+ require "ruby-next/core/kernel/then"
153
+
154
+ require "ruby-next/core/proc/compose"
155
+
156
+ require "ruby-next/core/enumerable/tally"
157
+ require "ruby-next/core/enumerable/filter"
158
+ require "ruby-next/core/enumerable/filter_map"
159
+
160
+ require "ruby-next/core/enumerator/produce"
161
+
162
+ require "ruby-next/core/array/difference_union_intersection"
163
+
164
+ require "ruby-next/core/hash/merge"
165
+
166
+ require "ruby-next/core/string/split"
167
+
168
+ require "ruby-next/core/symbol/start_with"
169
+ require "ruby-next/core/symbol/end_with"
170
+
171
+ require "ruby-next/core/unboundmethod/bind_call"
172
+
173
+ require "ruby-next/core/time/floor"
174
+ require "ruby-next/core/time/ceil"
175
+
176
+ # Core extensions required for pattern matching
177
+ # Required for pattern matching with refinements
178
+ unless defined?(NoMatchingPatternError)
179
+ class NoMatchingPatternError < StandardError
180
+ end
181
+ end
182
+
183
+ require "ruby-next/core/constants/no_matching_pattern_error"
184
+ require "ruby-next/core/array/deconstruct"
185
+ require "ruby-next/core/hash/deconstruct_keys"
186
+ require "ruby-next/core/struct/deconstruct"
187
+ require "ruby-next/core/struct/deconstruct_keys"
188
+
189
+ require "ruby-next/core/hash/except"
190
+
191
+ # Generate refinements
192
+ RubyNext.module_eval do
193
+ RubyNext::Core.patches.refined.each do |mod, patches|
194
+ # Only refine modules when supported
195
+ next unless mod.is_a?(Class) || RubyNext::Utils.refine_modules?
196
+
197
+ refine mod do
198
+ patches.each do |patch|
199
+ module_eval(patch.body, *patch.location)
200
+ end
201
+ end
202
+ end
203
+ end
@@ -1,41 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "ruby-next/version"
4
-
5
- module RubyNext
6
- # Mininum Ruby version supported by RubyNext
7
- MIN_SUPPORTED_VERSION = Gem::Version.new("2.2.0")
8
-
9
- # Where to store transpiled files (relative from the project LOAD_PATH, usually `lib/`)
10
- RUBY_NEXT_DIR = ".rbnext"
11
-
12
- # Defines last minor version for every major version
13
- LAST_MINOR_VERSIONS = {
14
- 2 => 8, # 2.8 is required for backward compatibility: some gems already uses it
15
- 3 => 0
16
- }.freeze
17
-
18
- LATEST_VERSION = [3, 0].freeze
19
-
20
- class << self
21
- def next_version(version = RUBY_VERSION)
22
- major, minor = Gem::Version.new(version).segments.map(&:to_i)
23
-
24
- return if major >= LATEST_VERSION.first && minor >= LATEST_VERSION.last
25
-
26
- nxt =
27
- if LAST_MINOR_VERSIONS[major] == minor
28
- "#{major + 1}.0.0"
29
- else
30
- "#{major}.#{minor + 1}.0"
31
- end
32
-
33
- Gem::Version.new(nxt)
34
- end
35
- end
36
-
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"
41
- end
4
+ require "ruby-next/config"
5
+ require "ruby-next/setup_self"
6
+ require "ruby-next/core"
7
+ require "ruby-next/core_ext" if RubyNext::Core.core_ext?
8
+ require "ruby-next/logging"