ruby-next-core 0.13.3 → 0.15.0

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