ruby-next-core 0.13.3 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
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