ruby-next-core 0.13.3 → 0.15.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -0
  3. data/README.md +65 -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 +44 -23
  7. data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +15 -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} +121 -34
  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 +14 -2
  13. data/lib/.rbnext/2.7/ruby-next/core.rb +7 -1
  14. data/lib/.rbnext/2.7/ruby-next/language/rewriters/2.7/pattern_matching.rb +1061 -0
  15. data/lib/ruby-next/commands/nextify.rb +15 -2
  16. data/lib/ruby-next/config.rb +2 -2
  17. data/lib/ruby-next/core/enumerable/compact.rb +22 -0
  18. data/lib/ruby-next/core/integer/try_convert.rb +16 -0
  19. data/lib/ruby-next/core/matchdata/match.rb +9 -0
  20. data/lib/ruby-next/core/refinement/import.rb +60 -0
  21. data/lib/ruby-next/core.rb +7 -1
  22. data/lib/ruby-next/irb.rb +24 -0
  23. data/lib/ruby-next/language/eval.rb +1 -0
  24. data/lib/ruby-next/language/rewriters/{numeric_literals.rb → 2.1/numeric_literals.rb} +0 -0
  25. data/lib/ruby-next/language/rewriters/{required_kwargs.rb → 2.1/required_kwargs.rb} +0 -0
  26. data/lib/ruby-next/language/rewriters/{safe_navigation.rb → 2.3/safe_navigation.rb} +0 -0
  27. data/lib/ruby-next/language/rewriters/{squiggly_heredoc.rb → 2.3/squiggly_heredoc.rb} +0 -0
  28. data/lib/ruby-next/language/rewriters/{runtime → 2.4}/dir.rb +0 -0
  29. data/lib/ruby-next/language/rewriters/2.5/rescue_within_block.rb +39 -0
  30. data/lib/ruby-next/language/rewriters/{endless_range.rb → 2.6/endless_range.rb} +0 -0
  31. data/lib/ruby-next/language/rewriters/{args_forward.rb → 2.7/args_forward.rb} +0 -0
  32. data/lib/ruby-next/language/rewriters/{numbered_params.rb → 2.7/numbered_params.rb} +0 -0
  33. data/lib/ruby-next/language/rewriters/{pattern_matching.rb → 2.7/pattern_matching.rb} +121 -34
  34. data/lib/ruby-next/language/rewriters/{args_forward_leading.rb → 3.0/args_forward_leading.rb} +0 -0
  35. data/lib/ruby-next/language/rewriters/{endless_method.rb → 3.0/endless_method.rb} +15 -12
  36. data/lib/ruby-next/language/rewriters/{find_pattern.rb → 3.0/find_pattern.rb} +1 -3
  37. data/lib/ruby-next/language/rewriters/3.0/in_pattern.rb +22 -0
  38. data/lib/ruby-next/language/rewriters/3.1/anonymous_block.rb +68 -0
  39. data/lib/ruby-next/language/rewriters/3.1/endless_method_command.rb +46 -0
  40. data/lib/ruby-next/language/rewriters/3.1/oneline_pattern_parensless.rb +41 -0
  41. data/lib/ruby-next/language/rewriters/3.1/pin_vars_pattern.rb +50 -0
  42. data/lib/ruby-next/language/rewriters/3.1/refinement_import_methods.rb +60 -0
  43. data/lib/ruby-next/language/rewriters/{shorthand_hash.rb → 3.1/shorthand_hash.rb} +6 -4
  44. data/lib/ruby-next/language/rewriters/base.rb +14 -2
  45. data/lib/ruby-next/language/{edge.rb → rewriters/edge.rb} +0 -0
  46. data/lib/ruby-next/language/rewriters/proposed/bind_vars_pattern.rb +50 -0
  47. data/lib/ruby-next/language/rewriters/{method_reference.rb → proposed/method_reference.rb} +0 -0
  48. data/lib/ruby-next/language/rewriters/proposed.rb +9 -0
  49. data/lib/ruby-next/language/rewriters/runtime.rb +1 -2
  50. data/lib/ruby-next/language/setup.rb +1 -1
  51. data/lib/ruby-next/language.rb +44 -23
  52. data/lib/ruby-next/pry.rb +90 -0
  53. data/lib/ruby-next/rubocop.rb +24 -4
  54. data/lib/ruby-next/version.rb +1 -1
  55. data/lib/uby-next/irb.rb +3 -0
  56. data/lib/uby-next/pry.rb +3 -0
  57. metadata +40 -43
  58. data/lib/ruby-next/language/proposed.rb +0 -9
  59. 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: 28083aad6fa1b32e00c885533e4c9c732a2401057f9ed6a939cd78ff3b1b58bb
4
+ data.tar.gz: 55734dd6e38699ff7e03c2723633ecd063f8fd97fe8878ca553772dbdac08ab3
5
5
  SHA512:
6
- metadata.gz: dd6760a195e2ca5aa0b47295069526fbe67aa183e28dc36d6076aa3fc0d3b58411320d043f1c7f2a9c19803193b6b84ff4a640cfa0fe3e14d813032340fe0ad4
7
- data.tar.gz: 6f2bf81c74312a1e1e3b68ac24df8ca2eb7ce679f8de9c8009c9550e880b0f8a0c18d7532f0570e003cd9511502af6607219f44e065be8d54d975a44e5dd12e5
6
+ metadata.gz: 7d42096d6a616470a402493894d5a7e65b7f08fd935dc8acfd0b91bf66016030edc93fd9c742aeed2fe8653dceb51c7b08c669e48ed1ff11373202cd738c9abc
7
+ data.tar.gz: a5740b48cb70283a3848d9415205baf7d17f4ee859562937b7f597bea0de43edba09f59bead4418284b395e6132cec92222b93cb1c1dc0c985adc3eda7b36986
data/CHANGELOG.md CHANGED
@@ -2,6 +2,50 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.15.0 (2022-03-21)
6
+
7
+ - Support IRB ([@palkan][])
8
+
9
+ - Create empty `.rbnext` folder during `nextify` if nothing to transpile. ([@palkan][])
10
+
11
+ This would prevent from auto-transpiling a library every time when no files should be transpiled.
12
+
13
+ - Auto-transpile using the current Ruby version. ([@palkan][])
14
+
15
+ - Support Pry. ([@baygeldin][])
16
+
17
+ - Add `rescue/ensure/else` within block rewriter for Ruby < 2.5. ([@fargelus][])
18
+
19
+ ## 0.14.1 (2022-01-21)
20
+
21
+ - Fix nested find patterns transpiling. ([@palkan][])
22
+
23
+ ## 0.14.0 🎄
24
+
25
+ - Add `Integer.try_convert`. ([@palkan][])
26
+
27
+ - Add `Enumerable#compact`, `Enumerator::Lazy#compact`. ([@palkan][])
28
+
29
+ - [Proposed] Add support for binding instance, class, global variables in pattern matching. ([@palkan][])
30
+
31
+ This brings back rightward assignment: `42 => @v`.
32
+
33
+ - Add `MatchData#match`. ([@palkan][])
34
+
35
+ - Add support for command syntax in endless methods (`def foo() = puts "bar"`)
36
+
37
+ - Add `Refinement#import_methods` support. ([@palkan][])
38
+
39
+ 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).
40
+
41
+ You can find the details in [the PR](https://github.com/ruby-next/ruby-next/pull/85).
42
+
43
+ - Added support for instance, class and global variables and expressions for pin operator. ([@palkan][])
44
+
45
+ - Support omitting parentheses in one-line pattern matching. ([@palkan][])
46
+
47
+ - Anonymous blocks `def b(&); c(&); end` ([@palkan][]).
48
+
5
49
  ## 0.13.3 (2021-12-08)
6
50
 
7
51
  - Revert 0.13.2 and freeze Parser version. ([@skryukov][])
data/README.md CHANGED
@@ -71,8 +71,11 @@ _Please, submit a PR to add your project to the list!_
71
71
  - [`ruby -ruby-next`](#uby-next)
72
72
  - [Logging & Debugging](#logging-and-debugging)
73
73
  - [RuboCop](#rubocop)
74
+ - [Using with IRB](#irb)
75
+ - [Using with Pry](#pry)
74
76
  - [Using with EOL Rubies](#using-with-eol-rubies)
75
77
  - [Proposed & edge features](#proposed-and-edge-features)
78
+ - [Known limitations](#known-limitations)
76
79
 
77
80
  ## Overview
78
81
 
@@ -200,8 +203,6 @@ You can change the transpiler mode:
200
203
  - Via environmental variable `RUBY_NEXT_TRANSPILE_MODE=ast`.
201
204
  - Via CLI option ([see below](#cli)).
202
205
 
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
206
  ## CLI
206
207
 
207
208
  Ruby Next ships with the command-line interface (`ruby-next`) which provides the following functionality:
@@ -231,7 +232,7 @@ Usage: ruby-next nextify DIRECTORY_OR_FILE [options]
231
232
 
232
233
  The behaviour depends on whether you transpile a single file or a directory:
233
234
 
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`).
235
+ - 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
236
 
236
237
  - When transpiling a file and providing the output path as a _file_ path, only a single version is created. For example:
237
238
 
@@ -283,6 +284,8 @@ $ ruby-next core_ext -l --name=filter --name=deconstruct
283
284
  - EnumeratorLazyFilterMap
284
285
  - HashDeconstructKeys
285
286
  - StructDeconstruct
287
+
288
+ ...
286
289
  ```
287
290
 
288
291
  ### CLI configuration file
@@ -453,6 +456,38 @@ AllCops:
453
456
 
454
457
  **NOTE:** you need `ruby-next` gem available in the environment where you run RuboCop (having `ruby-next-core` is not enough).
455
458
 
459
+ ## IRB
460
+
461
+ Ruby Next supports IRB. In order to enable edge Ruby features for your REPL, add the following line to your `.irbrc`:
462
+
463
+ ```ruby
464
+ require "ruby-next/irb"
465
+ ```
466
+
467
+ Alternatively, you can require it at startup:
468
+
469
+ ```sh
470
+ irb -r ruby-next/irb
471
+ # or
472
+ irb -ruby-next/irb
473
+ ```
474
+
475
+ ## Pry
476
+
477
+ Ruby Next supports Pry. In order to enable edge Ruby features for your REPL, add the following line to your `.pryrc`:
478
+
479
+ ```ruby
480
+ require "ruby-next/pry"
481
+ ```
482
+
483
+ Alternatively, you can require it at startup:
484
+
485
+ ```sh
486
+ pry -r ruby-next/pry
487
+ # or
488
+ pry -ruby-next/pry
489
+ ```
490
+
456
491
  ## Using with EOL Rubies
457
492
 
458
493
  We currently provide support for Ruby 2.2, 2.3 and 2.4.
@@ -522,13 +557,38 @@ require "ruby-next/language/runtime"
522
557
 
523
558
  ### Supported edge features
524
559
 
525
- `Array#intersect?` ([#15198](https://bugs.ruby-lang.org/issues/15198))
560
+ It's too early, Ruby 3.1 has just been released. See its features in the [supported features list](./SUPPORTED_FEATURES.md).
526
561
 
527
562
  ### Supported proposed features
528
563
 
529
564
  - _Method reference_ operator (`.:`) ([#13581](https://bugs.ruby-lang.org/issues/13581)).
565
+ - Binding non-local variables in pattern matching (`42 => @v`) ([#18408](https://bugs.ruby-lang.org/issues/18408)).
566
+
567
+ ## Known limitations
568
+
569
+ Ruby Next aims to be _reasonably compatible_ with MRI. That means, some edge cases could be uncovered. Below is the list of known limitations.
570
+
571
+ For gem authors, we recommend testing against all supported versions on CI to make sure you're not hit by edge cases.
572
+
573
+ ### Enumerable methods
574
+
575
+ 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.
576
+ To eliminate this, we also refine Array (when appropriate), but other enumerables could be affected.
577
+
578
+ See [this issue](https://bugs.ruby-lang.org/issues/13446) for details.
579
+
580
+ ### `Refinement#import_methods`
581
+
582
+ - Doesn't support importing methods generated with `eval`.
583
+ - Doesn't support aliases (both `alias` and `alias_method`).
584
+ - In JRuby, importing attribute accessors/readers/writers is not supported.
585
+ - When using AST transpiling in runtime, likely fails to import methods from a transpiled files (due to the updated source location).
586
+
587
+ See the [original PR](https://github.com/ruby-next/ruby-next/pull/85) for more details.
588
+
589
+ ### Other
530
590
 
531
- - Shorthand Hash/kwarg notation (`data = {x, y}` or `foo(x:, y:)`) ([#15236](https://bugs.ruby-lang.org/issues/15236)).
591
+ See [Parser's known issues](https://github.com/whitequark/parser#known-issues).
532
592
 
533
593
  ## Contributing
534
594
 
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,75 @@ 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
185
-
186
- require "ruby-next/language/rewriters/required_kwargs"
187
- rewriters << Rewriters::RequiredKwargs
189
+ require "ruby-next/language/rewriters/2.5/rescue_within_block"
190
+ rewriters << Rewriters::RescueWithinBlock
188
191
 
189
- require "ruby-next/language/rewriters/args_forward"
192
+ require "ruby-next/language/rewriters/2.7/args_forward"
190
193
  rewriters << Rewriters::ArgsForward
191
194
 
192
- # Must be added after general args forward rewriter to become
193
- # no-op in Ruby <2.7
194
- require "ruby-next/language/rewriters/args_forward_leading"
195
- rewriters << Rewriters::ArgsForwardLeading
196
-
197
- require "ruby-next/language/rewriters/numbered_params"
195
+ require "ruby-next/language/rewriters/2.7/numbered_params"
198
196
  rewriters << Rewriters::NumberedParams
199
197
 
200
- require "ruby-next/language/rewriters/pattern_matching"
198
+ require "ruby-next/language/rewriters/2.7/pattern_matching"
201
199
  rewriters << Rewriters::PatternMatching
202
200
 
201
+ # Must be added after general args forward rewriter to become
202
+ # no-op in Ruby <2.7
203
+ require "ruby-next/language/rewriters/3.0/args_forward_leading"
204
+ rewriters << Rewriters::ArgsForwardLeading
205
+
203
206
  # Must be added after general pattern matching rewriter to become
204
207
  # no-op in Ruby <2.7
205
- require "ruby-next/language/rewriters/find_pattern"
208
+ require "ruby-next/language/rewriters/3.0/find_pattern"
206
209
  rewriters << Rewriters::FindPattern
207
210
 
208
- require "ruby-next/language/rewriters/in_pattern"
211
+ require "ruby-next/language/rewriters/3.0/in_pattern"
209
212
  rewriters << Rewriters::InPattern
210
213
 
214
+ require "ruby-next/language/rewriters/3.0/endless_method"
215
+ RubyNext::Language.rewriters << RubyNext::Language::Rewriters::EndlessMethod
216
+
217
+ require "ruby-next/language/rewriters/3.1/oneline_pattern_parensless"
218
+ rewriters << Rewriters::OnelinePatternParensless
219
+
220
+ require "ruby-next/language/rewriters/3.1/pin_vars_pattern"
221
+ rewriters << Rewriters::PinVarsPattern
222
+
223
+ require "ruby-next/language/rewriters/3.1/anonymous_block"
224
+ rewriters << Rewriters::AnonymousBlock
225
+
226
+ require "ruby-next/language/rewriters/3.1/refinement_import_methods"
227
+ rewriters << Rewriters::RefinementImportMethods
228
+
229
+ require "ruby-next/language/rewriters/3.1/endless_method_command"
230
+ rewriters << Rewriters::EndlessMethodCommand
231
+
232
+ require "ruby-next/language/rewriters/3.1/shorthand_hash"
233
+ rewriters << RubyNext::Language::Rewriters::ShorthandHash
234
+
211
235
  # Put endless range in the end, 'cause Parser fails to parse it in
212
236
  # pattern matching
213
- require "ruby-next/language/rewriters/endless_range"
237
+ require "ruby-next/language/rewriters/2.6/endless_range"
214
238
  rewriters << Rewriters::EndlessRange
215
239
 
216
- require "ruby-next/language/rewriters/endless_method"
217
- RubyNext::Language.rewriters << RubyNext::Language::Rewriters::EndlessMethod
218
-
219
240
  if ENV["RUBY_NEXT_EDGE"] == "1"
220
- require "ruby-next/language/edge"
241
+ require "ruby-next/language/rewriters/edge"
221
242
  end
222
243
 
223
244
  if ENV["RUBY_NEXT_PROPOSED"] == "1"
224
- require "ruby-next/language/proposed"
245
+ require "ruby-next/language/rewriters/proposed"
225
246
  end
226
247
  end
227
248
  end
@@ -24,6 +24,8 @@ module RubyNext
24
24
  contents = File.read(path)
25
25
  transpile path, contents
26
26
  end
27
+
28
+ ensure_rbnext!
27
29
  end
28
30
 
29
31
  def parse!(args)
@@ -48,11 +50,11 @@ module RubyNext
48
50
  end
49
51
 
50
52
  opts.on("--edge", "Enable edge (master) Ruby features") do |val|
51
- require "ruby-next/language/edge"
53
+ require "ruby-next/language/rewriters/edge"
52
54
  end
53
55
 
54
56
  opts.on("--proposed", "Enable proposed/experimental Ruby features") do |val|
55
- require "ruby-next/language/proposed"
57
+ require "ruby-next/language/rewriters/proposed"
56
58
  end
57
59
 
58
60
  opts.on(
@@ -185,6 +187,17 @@ module RubyNext
185
187
  FileUtils.rm_r(next_dir_path)
186
188
  end
187
189
 
190
+ def ensure_rbnext!
191
+ return if CLI.dry_run? || stdout?
192
+
193
+ return if File.directory?(next_dir_path)
194
+
195
+ return if next_dir_path.end_with?(".rb")
196
+
197
+ FileUtils.mkdir_p next_dir_path
198
+ File.write(File.join(next_dir_path, ".keep"), "")
199
+ end
200
+
188
201
  def next_dir_path
189
202
  @next_dir_path ||= (out_path || File.join(lib_path, RUBY_NEXT_DIR))
190
203
  end
@@ -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
@@ -243,6 +243,7 @@ module RubyNext
243
243
 
244
244
  @deconstructed_keys = {}
245
245
  @predicates = Predicates::CaseIn.new
246
+ @lvars = []
246
247
 
247
248
  matchee_ast =
248
249
  s(:begin, s(:lvasgn, MATCHEE, node.children[0]))
@@ -272,6 +273,7 @@ module RubyNext
272
273
 
273
274
  @deconstructed_keys = {}
274
275
  @predicates = Predicates::Noop.new
276
+ @lvars = []
275
277
 
276
278
  matchee =
277
279
  s(:begin, s(:lvasgn, MATCHEE, node.children[0]))
@@ -282,10 +284,12 @@ module RubyNext
282
284
  arr: MATCHEE_ARR,
283
285
  hash: MATCHEE_HASH
284
286
  ) do
285
- send(
286
- :"#{node.children[1].type}_clause",
287
- node.children[1]
288
- ).then do |node|
287
+ with_declared_locals do
288
+ send(
289
+ :"#{node.children[1].type}_clause",
290
+ node.children[1]
291
+ )
292
+ end.then do |node|
289
293
  s(:begin,
290
294
  s(:or,
291
295
  node,
@@ -306,6 +310,41 @@ module RubyNext
306
310
 
307
311
  alias on_in_match on_match_pattern
308
312
 
313
+ def on_match_pattern_p(node)
314
+ context.track! self
315
+
316
+ @deconstructed_keys = {}
317
+ @predicates = Predicates::Noop.new
318
+ @lvars = []
319
+
320
+ matchee =
321
+ s(:begin, s(:lvasgn, MATCHEE, node.children[0]))
322
+
323
+ pattern =
324
+ locals.with(
325
+ matchee: MATCHEE,
326
+ arr: MATCHEE_ARR,
327
+ hash: MATCHEE_HASH
328
+ ) do
329
+ with_declared_locals do
330
+ send(
331
+ :"#{node.children[1].type}_clause",
332
+ node.children[1]
333
+ )
334
+ end
335
+ end
336
+
337
+ node.updated(
338
+ :and,
339
+ [
340
+ matchee,
341
+ pattern
342
+ ]
343
+ ).tap do |new_node|
344
+ replace(node.loc.expression, inline_blocks(unparse(new_node)))
345
+ end
346
+ end
347
+
309
348
  private
310
349
 
311
350
  def rewrite_case_in!(node, matchee, new_node)
@@ -363,13 +402,15 @@ module RubyNext
363
402
  def build_when_clause(clause)
364
403
  predicates.reset!
365
404
  [
366
- with_guard(
367
- send(
368
- :"#{clause.children[0].type}_clause",
369
- clause.children[0]
370
- ),
371
- clause.children[1] # guard
372
- ),
405
+ with_declared_locals do
406
+ with_guard(
407
+ send(
408
+ :"#{clause.children[0].type}_clause",
409
+ clause.children[0]
410
+ ),
411
+ clause.children[1] # guard
412
+ )
413
+ end,
373
414
  process(clause.children[2] || s(:nil)) # expression
374
415
  ].then do |children|
375
416
  s(:when, *children)
@@ -407,13 +448,14 @@ module RubyNext
407
448
  end
408
449
 
409
450
  def match_var_clause(node, left = s(:lvar, locals[:matchee]))
410
- return s(:true) if node.children[0] == :_
451
+ var = node.children[0]
452
+ return s(:true) if var == :_
411
453
 
412
- check_match_var_alternation! node.children[0]
454
+ check_match_var_alternation!(var)
413
455
 
414
456
  s(:begin,
415
457
  s(:or,
416
- s(:begin, s(:lvasgn, node.children[0], left)),
458
+ s(:begin, build_var_assignment(var, left)),
417
459
  s(:true)))
418
460
  end
419
461
 
@@ -508,11 +550,10 @@ module RubyNext
508
550
  def array_find(head, *nodes, tail)
509
551
  index = s(:lvar, :__i__)
510
552
 
511
- match_vars = []
512
-
513
553
  head_match =
514
554
  unless head.children.empty?
515
- match_vars << s(:lvasgn, head.children[0].children[0])
555
+ # we only need to call this to track the lvar usage
556
+ build_var_assignment(head.children[0].children[0])
516
557
 
517
558
  arr_take = s(:send,
518
559
  s(:lvar, locals[:arr]),
@@ -524,16 +565,19 @@ module RubyNext
524
565
 
525
566
  tail_match =
526
567
  unless tail.children.empty?
527
- match_vars << s(:lvasgn, tail.children[0].children[0])
568
+ # we only need to call this to track the lvar usage
569
+ build_var_assignment(tail.children[0].children[0])
528
570
 
529
571
  match_var_clause(tail.children[0], arr_slice(index + nodes.size, -1))
530
572
  end
531
573
 
532
574
  nodes.each do |node|
533
575
  if node.type == :match_var
534
- match_vars << s(:lvasgn, node.children[0])
576
+ # we only need to call this to track the lvar usage
577
+ build_var_assignment(node.children[0])
535
578
  elsif node.type == :match_as
536
- match_vars << s(:lvasgn, node.children[1].children[0])
579
+ # we only need to call this to track the lvar usage
580
+ build_var_assignment(node.children[1].children[0])
537
581
  end
538
582
  end
539
583
 
@@ -561,19 +605,7 @@ module RubyNext
561
605
  s(:args,
562
606
  s(:arg, :_),
563
607
  s(:arg, :__i__)),
564
- pattern).then do |block|
565
- next block if match_vars.empty?
566
-
567
- # We need to declare match vars outside of `find` block
568
- locals_declare = s(:begin, s(:masgn,
569
- s(:mlhs, *match_vars),
570
- s(:nil)))
571
-
572
- s(:begin,
573
- s(:or,
574
- locals_declare,
575
- block))
576
- end
608
+ pattern)
577
609
  end
578
610
 
579
611
  def array_match_rest(index, node, *tail)
@@ -616,6 +648,14 @@ module RubyNext
616
648
  end
617
649
  end
618
650
 
651
+ def find_pattern_array_element(node, index)
652
+ element = arr_item_at(index)
653
+ locals.with(arr: locals[:arr, index]) do
654
+ predicates.push :"i#{index}"
655
+ find_pattern_clause(node, element).tap { predicates.pop }
656
+ end
657
+ end
658
+
619
659
  def hash_pattern_array_element(node, index)
620
660
  element = arr_item_at(index)
621
661
  locals.with(hash: locals[:arr, index]) do
@@ -796,6 +836,15 @@ module RubyNext
796
836
  end
797
837
  end
798
838
 
839
+ def find_pattern_hash_element(node, key)
840
+ element = hash_value_at(key)
841
+ key_index = deconstructed_key(key)
842
+ locals.with(arr: locals[:hash, key_index]) do
843
+ predicates.push :"k#{key_index}"
844
+ find_pattern_clause(node, element).tap { predicates.pop }
845
+ end
846
+ end
847
+
799
848
  def hash_element(head, *tail)
800
849
  send("#{head.type}_hash_element", head).then do |node|
801
850
  next node if tail.empty?
@@ -861,6 +910,10 @@ module RubyNext
861
910
  match_var_clause(child, s(:lvar, locals[:hash]))
862
911
  end
863
912
 
913
+ def pin_hash_element(node, index)
914
+ case_eq_hash_element node.children[0], index
915
+ end
916
+
864
917
  def case_eq_hash_element(node, key)
865
918
  case_eq_clause node, hash_value_at(key)
866
919
  end
@@ -911,6 +964,24 @@ module RubyNext
911
964
  end
912
965
  end
913
966
 
967
+ def with_declared_locals
968
+ lvars.clear
969
+ node = yield
970
+
971
+ return node if lvars.empty?
972
+
973
+ # We need to declare match lvars outside of the outer `find` block,
974
+ # so we do that for that whole pattern
975
+ locals_declare = s(:begin, s(:masgn,
976
+ s(:mlhs, *lvars.uniq.map { |_1| s(:lvasgn, _1) }),
977
+ s(:nil)))
978
+
979
+ s(:begin,
980
+ s(:or,
981
+ locals_declare,
982
+ node))
983
+ end
984
+
914
985
  def no_matching_pattern
915
986
  raise_error(
916
987
  :NoMatchingPatternError,
@@ -945,13 +1016,17 @@ module RubyNext
945
1016
 
946
1017
  private
947
1018
 
948
- attr_reader :deconstructed_keys, :predicates
1019
+ attr_reader :deconstructed_keys, :predicates, :lvars
949
1020
 
950
1021
  # Raise SyntaxError if match-var is used within alternation
951
1022
  # https://github.com/ruby/ruby/blob/672213ef1ca2b71312084057e27580b340438796/compile.c#L5900
952
1023
  def check_match_var_alternation!(name)
953
1024
  return unless locals.key?(ALTERNATION_MARKER)
954
1025
 
1026
+ if name.is_a?(::Parser::AST::Node)
1027
+ raise ::SyntaxError, "illegal variable in alternative pattern (#{name.children.first})"
1028
+ end
1029
+
955
1030
  return if name.start_with?("_")
956
1031
 
957
1032
  raise ::SyntaxError, "illegal variable in alternative pattern (#{name})"
@@ -968,6 +1043,18 @@ module RubyNext
968
1043
  def inline_blocks(source)
969
1044
  source.gsub(/(?:do|{) \|_, __i__\|\n\s*([^\n]+)\n\s*(?:end|})/, '{ |_, __i__| \1 }')
970
1045
  end
1046
+
1047
+ # Value could be omitted for mass assignment
1048
+ def build_var_assignment(var, value = nil)
1049
+ unless var.is_a?(::Parser::AST::Node)
1050
+ lvars << var
1051
+ return s(:lvasgn, *[var, value].compact)
1052
+ end
1053
+
1054
+ asign_type = :"#{var.type.to_s[0]}vasgn"
1055
+
1056
+ s(asign_type, *[var.children[0], value].compact)
1057
+ end
971
1058
  end
972
1059
  end
973
1060
  end