ruby-next-core 0.13.3 → 0.14.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -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} +53 -7
  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/ruby-next/commands/nextify.rb +2 -2
  15. data/lib/ruby-next/config.rb +2 -2
  16. data/lib/ruby-next/core/enumerable/compact.rb +22 -0
  17. data/lib/ruby-next/core/integer/try_convert.rb +16 -0
  18. data/lib/ruby-next/core/matchdata/match.rb +9 -0
  19. data/lib/ruby-next/core/refinement/import.rb +60 -0
  20. data/lib/ruby-next/core.rb +7 -1
  21. data/lib/ruby-next/language/eval.rb +1 -0
  22. data/lib/ruby-next/language/rewriters/{numeric_literals.rb → 2.1/numeric_literals.rb} +0 -0
  23. data/lib/ruby-next/language/rewriters/{required_kwargs.rb → 2.1/required_kwargs.rb} +0 -0
  24. data/lib/ruby-next/language/rewriters/{safe_navigation.rb → 2.3/safe_navigation.rb} +0 -0
  25. data/lib/ruby-next/language/rewriters/{squiggly_heredoc.rb → 2.3/squiggly_heredoc.rb} +0 -0
  26. data/lib/ruby-next/language/rewriters/{runtime → 2.4}/dir.rb +0 -0
  27. data/lib/ruby-next/language/rewriters/{endless_range.rb → 2.6/endless_range.rb} +0 -0
  28. data/lib/ruby-next/language/rewriters/{args_forward.rb → 2.7/args_forward.rb} +0 -0
  29. data/lib/ruby-next/language/rewriters/{numbered_params.rb → 2.7/numbered_params.rb} +0 -0
  30. data/lib/ruby-next/language/rewriters/{pattern_matching.rb → 2.7/pattern_matching.rb} +53 -7
  31. data/lib/ruby-next/language/rewriters/{args_forward_leading.rb → 3.0/args_forward_leading.rb} +0 -0
  32. data/lib/ruby-next/language/rewriters/{endless_method.rb → 3.0/endless_method.rb} +15 -12
  33. data/lib/ruby-next/language/rewriters/{find_pattern.rb → 3.0/find_pattern.rb} +1 -3
  34. data/lib/ruby-next/language/rewriters/3.0/in_pattern.rb +22 -0
  35. data/lib/ruby-next/language/rewriters/3.1/anonymous_block.rb +68 -0
  36. data/lib/ruby-next/language/rewriters/3.1/endless_method_command.rb +46 -0
  37. data/lib/ruby-next/language/rewriters/3.1/oneline_pattern_parensless.rb +41 -0
  38. data/lib/ruby-next/language/rewriters/3.1/pin_vars_pattern.rb +50 -0
  39. data/lib/ruby-next/language/rewriters/3.1/refinement_import_methods.rb +60 -0
  40. data/lib/ruby-next/language/rewriters/{shorthand_hash.rb → 3.1/shorthand_hash.rb} +6 -4
  41. data/lib/ruby-next/language/rewriters/base.rb +13 -1
  42. data/lib/ruby-next/language/{edge.rb → rewriters/edge.rb} +0 -0
  43. data/lib/ruby-next/language/rewriters/proposed/bind_vars_pattern.rb +50 -0
  44. data/lib/ruby-next/language/rewriters/{method_reference.rb → proposed/method_reference.rb} +0 -0
  45. data/lib/ruby-next/language/rewriters/proposed.rb +9 -0
  46. data/lib/ruby-next/language/rewriters/runtime.rb +1 -2
  47. data/lib/ruby-next/language.rb +41 -23
  48. data/lib/ruby-next/rubocop.rb +24 -4
  49. data/lib/ruby-next/version.rb +1 -1
  50. metadata +34 -43
  51. data/lib/ruby-next/language/proposed.rb +0 -9
  52. data/lib/ruby-next/language/rewriters/in_pattern.rb +0 -56
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c37d70af44462c4f2a42db64ca27a9b99fa08e9278d5e308dd3ba5fb67aa7b3
4
- data.tar.gz: fb832052e7479e0c5719e57904b105f47721d1688e9ec47f4d58d7f75de9fffb
3
+ metadata.gz: 55cb050984d91ee15965b5629bd998d7300af1051e7ad721f1dfed77fc6df7c7
4
+ data.tar.gz: ac68a6f7da2b826b11a40be72948601f2d12ce8b1cd27ab9953977238c1e488d
5
5
  SHA512:
6
- metadata.gz: dd6760a195e2ca5aa0b47295069526fbe67aa183e28dc36d6076aa3fc0d3b58411320d043f1c7f2a9c19803193b6b84ff4a640cfa0fe3e14d813032340fe0ad4
7
- data.tar.gz: 6f2bf81c74312a1e1e3b68ac24df8ca2eb7ce679f8de9c8009c9550e880b0f8a0c18d7532f0570e003cd9511502af6607219f44e065be8d54d975a44e5dd12e5
6
+ metadata.gz: 7cd0de569637973ff61866733a6a3d59dac4e317bf5e522b23a88fc5af8941e4a072ae1dfbf26163a661c71911f7801545cad00134cd826d4913f684b03473e8
7
+ data.tar.gz: 2ebf8f0a42bd0771cfbc2f2240a3844436d3f108e5cec292ec2cd7e3bebef0a332088357a892cf3cd2b0c6835e9f39679cc472cd6a0ac4d120850194d8f2e783
data/CHANGELOG.md CHANGED
@@ -2,6 +2,32 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.14.0 🎄
6
+
7
+ - Add `Integer.try_convert`. ([@palkan][])
8
+
9
+ - Add `Enumerable#compact`, `Enumerator::Lazy#compact`. ([@palkan][])
10
+
11
+ - [Proposed] Add support for binding instance, class, global variables in pattern matching. ([@palkan][])
12
+
13
+ This brings back rightward assignment: `42 => @v`.
14
+
15
+ - Add `MatchData#match`. ([@palkan][])
16
+
17
+ - Add support for command syntax in endless methods (`def foo() = puts "bar"`)
18
+
19
+ - Add `Refinement#import_methods` support. ([@palkan][])
20
+
21
+ This API only works in conjunction with transpiling, since it couldn't be backported purely as a method and requires passing an additional argument (Binding).
22
+
23
+ You can find the details in [the PR](https://github.com/ruby-next/ruby-next/pull/85).
24
+
25
+ - Added support for instance, class and global variables and expressions for pin operator. ([@palkan][])
26
+
27
+ - Support omitting parentheses in one-line pattern matching. ([@palkan][])
28
+
29
+ - Anonymous blocks `def b(&); c(&); end` ([@palkan][]).
30
+
5
31
  ## 0.13.3 (2021-12-08)
6
32
 
7
33
  - Revert 0.13.2 and freeze Parser version. ([@skryukov][])
data/README.md CHANGED
@@ -73,6 +73,7 @@ _Please, submit a PR to add your project to the list!_
73
73
  - [RuboCop](#rubocop)
74
74
  - [Using with EOL Rubies](#using-with-eol-rubies)
75
75
  - [Proposed & edge features](#proposed-and-edge-features)
76
+ - [Known limitations](#known-limitations)
76
77
 
77
78
  ## Overview
78
79
 
@@ -200,8 +201,6 @@ You can change the transpiler mode:
200
201
  - Via environmental variable `RUBY_NEXT_TRANSPILE_MODE=ast`.
201
202
  - Via CLI option ([see below](#cli)).
202
203
 
203
- **NOTE:** For the time being, Unparser doesn't support Ruby 3.0 AST nodes, so we always use rewrite mode in Ruby 3.0+.
204
-
205
204
  ## CLI
206
205
 
207
206
  Ruby Next ships with the command-line interface (`ruby-next`) which provides the following functionality:
@@ -231,7 +230,7 @@ Usage: ruby-next nextify DIRECTORY_OR_FILE [options]
231
230
 
232
231
  The behaviour depends on whether you transpile a single file or a directory:
233
232
 
234
- - 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`).
233
+ - 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`, etc.). 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`).
235
234
 
236
235
  - When transpiling a file and providing the output path as a _file_ path, only a single version is created. For example:
237
236
 
@@ -283,6 +282,8 @@ $ ruby-next core_ext -l --name=filter --name=deconstruct
283
282
  - EnumeratorLazyFilterMap
284
283
  - HashDeconstructKeys
285
284
  - StructDeconstruct
285
+
286
+ ...
286
287
  ```
287
288
 
288
289
  ### CLI configuration file
@@ -522,13 +523,38 @@ require "ruby-next/language/runtime"
522
523
 
523
524
  ### Supported edge features
524
525
 
525
- `Array#intersect?` ([#15198](https://bugs.ruby-lang.org/issues/15198))
526
+ It's too early, Ruby 3.1 has just been released. See its features in the [supported features list](./SUPPORTED_FEATURES.md).
526
527
 
527
528
  ### Supported proposed features
528
529
 
529
530
  - _Method reference_ operator (`.:`) ([#13581](https://bugs.ruby-lang.org/issues/13581)).
531
+ - Binding non-local variables in pattern matching (`42 => @v`) ([#18408](https://bugs.ruby-lang.org/issues/18408)).
532
+
533
+ ## Known limitations
534
+
535
+ Ruby Next aims to be _reasonably compatible_ with MRI. That means, some edge cases could be uncovered. Below is the list of known limitations.
536
+
537
+ For gem authors, we recommend testing against all supported versions on CI to make sure you're not hit by edge cases.
538
+
539
+ ### Enumerable methods
540
+
541
+ Using refinements (`using RubyNext`) for modules could lead to unexpected behaviour in case there is also a `prepend` for the same module in Ruby <2.7.
542
+ To eliminate this, we also refine Array (when appropriate), but other enumerables could be affected.
543
+
544
+ See [this issue](https://bugs.ruby-lang.org/issues/13446) for details.
545
+
546
+ ### `Refinement#import_methods`
547
+
548
+ - Doesn't support importing methods generated with `eval`.
549
+ - Doesn't support aliases (both `alias` and `alias_method`).
550
+ - In JRuby, importing attribute accessors/readers/writers is not supported.
551
+ - When using AST transpiling in runtime, likely fails to import methods from a transpiled files (due to the updated source location).
552
+
553
+ See the [original PR](https://github.com/ruby-next/ruby-next/pull/85) for more details.
554
+
555
+ ### Other
530
556
 
531
- - Shorthand Hash/kwarg notation (`data = {x, y}` or `foo(x:, y:)`) ([#15236](https://bugs.ruby-lang.org/issues/15236)).
557
+ See [Parser's known issues](https://github.com/whitequark/parser#known-issues).
532
558
 
533
559
  ## Contributing
534
560
 
data/bin/transform CHANGED
@@ -34,9 +34,13 @@ OptionParser.new do |opts|
34
34
  transform_opts[:rewriters] = RubyNext::Language.current_rewriters
35
35
  end
36
36
 
37
- opts.on("--rewrite", "User rewrite transpiling mode") do
37
+ opts.on("--rewrite", "Use rewrite transpiling mode") do
38
38
  RubyNext::Language.mode = :rewrite
39
39
  end
40
+
41
+ opts.on("--ast", "Use AST transpiling mode") do
42
+ RubyNext::Language.mode = :ast
43
+ end
40
44
  end.parse!
41
45
 
42
46
  puts RubyNext::Language.transform(contents, **transform_opts)
@@ -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|
@@ -174,54 +174,72 @@ module RubyNext
174
174
 
175
175
  require "ruby-next/language/rewriters/base"
176
176
 
177
- require "ruby-next/language/rewriters/squiggly_heredoc"
177
+ require "ruby-next/language/rewriters/2.1/numeric_literals"
178
+ rewriters << Rewriters::NumericLiterals
179
+
180
+ require "ruby-next/language/rewriters/2.1/required_kwargs"
181
+ rewriters << Rewriters::RequiredKwargs
182
+
183
+ require "ruby-next/language/rewriters/2.3/squiggly_heredoc"
178
184
  rewriters << Rewriters::SquigglyHeredoc
179
185
 
180
- require "ruby-next/language/rewriters/safe_navigation"
186
+ require "ruby-next/language/rewriters/2.3/safe_navigation"
181
187
  rewriters << Rewriters::SafeNavigation
182
188
 
183
- require "ruby-next/language/rewriters/numeric_literals"
184
- rewriters << Rewriters::NumericLiterals
189
+ require "ruby-next/language/rewriters/2.7/args_forward"
190
+ rewriters << Rewriters::ArgsForward
185
191
 
186
- require "ruby-next/language/rewriters/required_kwargs"
187
- rewriters << Rewriters::RequiredKwargs
192
+ require "ruby-next/language/rewriters/2.7/numbered_params"
193
+ rewriters << Rewriters::NumberedParams
188
194
 
189
- require "ruby-next/language/rewriters/args_forward"
190
- rewriters << Rewriters::ArgsForward
195
+ require "ruby-next/language/rewriters/2.7/pattern_matching"
196
+ rewriters << Rewriters::PatternMatching
191
197
 
192
198
  # Must be added after general args forward rewriter to become
193
199
  # no-op in Ruby <2.7
194
- require "ruby-next/language/rewriters/args_forward_leading"
200
+ require "ruby-next/language/rewriters/3.0/args_forward_leading"
195
201
  rewriters << Rewriters::ArgsForwardLeading
196
202
 
197
- require "ruby-next/language/rewriters/numbered_params"
198
- rewriters << Rewriters::NumberedParams
199
-
200
- require "ruby-next/language/rewriters/pattern_matching"
201
- rewriters << Rewriters::PatternMatching
202
-
203
203
  # Must be added after general pattern matching rewriter to become
204
204
  # no-op in Ruby <2.7
205
- require "ruby-next/language/rewriters/find_pattern"
205
+ require "ruby-next/language/rewriters/3.0/find_pattern"
206
206
  rewriters << Rewriters::FindPattern
207
207
 
208
- require "ruby-next/language/rewriters/in_pattern"
208
+ require "ruby-next/language/rewriters/3.0/in_pattern"
209
209
  rewriters << Rewriters::InPattern
210
210
 
211
+ require "ruby-next/language/rewriters/3.0/endless_method"
212
+ RubyNext::Language.rewriters << RubyNext::Language::Rewriters::EndlessMethod
213
+
214
+ require "ruby-next/language/rewriters/3.1/oneline_pattern_parensless"
215
+ rewriters << Rewriters::OnelinePatternParensless
216
+
217
+ require "ruby-next/language/rewriters/3.1/pin_vars_pattern"
218
+ rewriters << Rewriters::PinVarsPattern
219
+
220
+ require "ruby-next/language/rewriters/3.1/anonymous_block"
221
+ rewriters << Rewriters::AnonymousBlock
222
+
223
+ require "ruby-next/language/rewriters/3.1/refinement_import_methods"
224
+ rewriters << Rewriters::RefinementImportMethods
225
+
226
+ require "ruby-next/language/rewriters/3.1/endless_method_command"
227
+ rewriters << Rewriters::EndlessMethodCommand
228
+
229
+ require "ruby-next/language/rewriters/3.1/shorthand_hash"
230
+ rewriters << RubyNext::Language::Rewriters::ShorthandHash
231
+
211
232
  # Put endless range in the end, 'cause Parser fails to parse it in
212
233
  # pattern matching
213
- require "ruby-next/language/rewriters/endless_range"
234
+ require "ruby-next/language/rewriters/2.6/endless_range"
214
235
  rewriters << Rewriters::EndlessRange
215
236
 
216
- require "ruby-next/language/rewriters/endless_method"
217
- RubyNext::Language.rewriters << RubyNext::Language::Rewriters::EndlessMethod
218
-
219
237
  if ENV["RUBY_NEXT_EDGE"] == "1"
220
- require "ruby-next/language/edge"
238
+ require "ruby-next/language/rewriters/edge"
221
239
  end
222
240
 
223
241
  if ENV["RUBY_NEXT_PROPOSED"] == "1"
224
- require "ruby-next/language/proposed"
242
+ require "ruby-next/language/rewriters/proposed"
225
243
  end
226
244
  end
227
245
  end
@@ -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(
@@ -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
@@ -306,6 +306,38 @@ module RubyNext
306
306
 
307
307
  alias on_in_match on_match_pattern
308
308
 
309
+ def on_match_pattern_p(node)
310
+ context.track! self
311
+
312
+ @deconstructed_keys = {}
313
+ @predicates = Predicates::Noop.new
314
+
315
+ matchee =
316
+ s(:begin, s(:lvasgn, MATCHEE, node.children[0]))
317
+
318
+ pattern =
319
+ locals.with(
320
+ matchee: MATCHEE,
321
+ arr: MATCHEE_ARR,
322
+ hash: MATCHEE_HASH
323
+ ) do
324
+ send(
325
+ :"#{node.children[1].type}_clause",
326
+ node.children[1]
327
+ )
328
+ end
329
+
330
+ node.updated(
331
+ :and,
332
+ [
333
+ matchee,
334
+ pattern
335
+ ]
336
+ ).tap do |new_node|
337
+ replace(node.loc.expression, inline_blocks(unparse(new_node)))
338
+ end
339
+ end
340
+
309
341
  private
310
342
 
311
343
  def rewrite_case_in!(node, matchee, new_node)
@@ -407,13 +439,14 @@ module RubyNext
407
439
  end
408
440
 
409
441
  def match_var_clause(node, left = s(:lvar, locals[:matchee]))
410
- return s(:true) if node.children[0] == :_
442
+ var = node.children[0]
443
+ return s(:true) if var == :_
411
444
 
412
- check_match_var_alternation! node.children[0]
445
+ check_match_var_alternation!(var) unless var.is_a?(::Parser::AST::Node)
413
446
 
414
447
  s(:begin,
415
448
  s(:or,
416
- s(:begin, s(:lvasgn, node.children[0], left)),
449
+ s(:begin, build_var_assignment(var, left)),
417
450
  s(:true)))
418
451
  end
419
452
 
@@ -512,7 +545,7 @@ module RubyNext
512
545
 
513
546
  head_match =
514
547
  unless head.children.empty?
515
- match_vars << s(:lvasgn, head.children[0].children[0])
548
+ match_vars << build_var_assignment(head.children[0].children[0])
516
549
 
517
550
  arr_take = s(:send,
518
551
  s(:lvar, locals[:arr]),
@@ -524,16 +557,16 @@ module RubyNext
524
557
 
525
558
  tail_match =
526
559
  unless tail.children.empty?
527
- match_vars << s(:lvasgn, tail.children[0].children[0])
560
+ match_vars << build_var_assignment(tail.children[0].children[0])
528
561
 
529
562
  match_var_clause(tail.children[0], arr_slice(index + nodes.size, -1))
530
563
  end
531
564
 
532
565
  nodes.each do |node|
533
566
  if node.type == :match_var
534
- match_vars << s(:lvasgn, node.children[0])
567
+ match_vars << build_var_assignment(node.children[0])
535
568
  elsif node.type == :match_as
536
- match_vars << s(:lvasgn, node.children[1].children[0])
569
+ match_vars << build_var_assignment(node.children[1].children[0])
537
570
  end
538
571
  end
539
572
 
@@ -861,6 +894,10 @@ module RubyNext
861
894
  match_var_clause(child, s(:lvar, locals[:hash]))
862
895
  end
863
896
 
897
+ def pin_hash_element(node, index)
898
+ case_eq_hash_element node.children[0], index
899
+ end
900
+
864
901
  def case_eq_hash_element(node, key)
865
902
  case_eq_clause node, hash_value_at(key)
866
903
  end
@@ -968,6 +1005,15 @@ module RubyNext
968
1005
  def inline_blocks(source)
969
1006
  source.gsub(/(?:do|{) \|_, __i__\|\n\s*([^\n]+)\n\s*(?:end|})/, '{ |_, __i__| \1 }')
970
1007
  end
1008
+
1009
+ # Value could be omitted for mass assignment
1010
+ def build_var_assignment(var, value = nil)
1011
+ return s(:lvasgn, *[var, value].compact) unless var.is_a?(::Parser::AST::Node)
1012
+
1013
+ asign_type = :"#{var.type.to_s[0]}vasgn"
1014
+
1015
+ s(asign_type, *[var.children[0], value].compact)
1016
+ end
971
1017
  end
972
1018
  end
973
1019
  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 ((((__safe_lvar__ = block) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.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 ((((__safe_lvar__ = block) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.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 ((((__safe_lvar__ = block) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.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
@@ -14,6 +14,18 @@ module RubyNext
14
14
 
15
15
  class Base < ::Parser::TreeRewriter
16
16
  class LocalsTracker
17
+ using(Module.new do
18
+ refine ::Parser::AST::Node do
19
+ def to_index
20
+ ((((__safe_lvar__ = children) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.first) || type
21
+ end
22
+ end
23
+
24
+ refine ::Object do
25
+ alias to_index itself
26
+ end
27
+ end)
28
+
17
29
  attr_reader :stacks
18
30
 
19
31
  def initialize
@@ -28,7 +40,7 @@ module RubyNext
28
40
  def [](name, suffix = nil)
29
41
  fetch(name).then do |name|
30
42
  next name unless suffix
31
- :"#{name}#{suffix}__"
43
+ :"#{name}#{suffix.to_index}__"
32
44
  end
33
45
  end
34
46
 
@@ -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|
@@ -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