ruby-next-core 0.13.1 → 0.14.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -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} +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 +13 -1
  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 +2 -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/language/eval.rb +1 -0
  23. data/lib/ruby-next/language/rewriters/{numeric_literals.rb → 2.1/numeric_literals.rb} +0 -0
  24. data/lib/ruby-next/language/rewriters/{required_kwargs.rb → 2.1/required_kwargs.rb} +0 -0
  25. data/lib/ruby-next/language/rewriters/{safe_navigation.rb → 2.3/safe_navigation.rb} +0 -0
  26. data/lib/ruby-next/language/rewriters/{squiggly_heredoc.rb → 2.3/squiggly_heredoc.rb} +0 -0
  27. data/lib/ruby-next/language/rewriters/{runtime → 2.4}/dir.rb +0 -0
  28. data/lib/ruby-next/language/rewriters/{endless_range.rb → 2.6/endless_range.rb} +0 -0
  29. data/lib/ruby-next/language/rewriters/{args_forward.rb → 2.7/args_forward.rb} +0 -0
  30. data/lib/ruby-next/language/rewriters/{numbered_params.rb → 2.7/numbered_params.rb} +0 -0
  31. data/lib/ruby-next/language/rewriters/{pattern_matching.rb → 2.7/pattern_matching.rb} +121 -34
  32. data/lib/ruby-next/language/rewriters/{args_forward_leading.rb → 3.0/args_forward_leading.rb} +0 -0
  33. data/lib/ruby-next/language/rewriters/{endless_method.rb → 3.0/endless_method.rb} +15 -12
  34. data/lib/ruby-next/language/rewriters/{find_pattern.rb → 3.0/find_pattern.rb} +1 -3
  35. data/lib/ruby-next/language/rewriters/3.0/in_pattern.rb +22 -0
  36. data/lib/ruby-next/language/rewriters/3.1/anonymous_block.rb +68 -0
  37. data/lib/ruby-next/language/rewriters/3.1/endless_method_command.rb +46 -0
  38. data/lib/ruby-next/language/rewriters/3.1/oneline_pattern_parensless.rb +41 -0
  39. data/lib/ruby-next/language/rewriters/3.1/pin_vars_pattern.rb +50 -0
  40. data/lib/ruby-next/language/rewriters/3.1/refinement_import_methods.rb +60 -0
  41. data/lib/ruby-next/language/rewriters/{shorthand_hash.rb → 3.1/shorthand_hash.rb} +6 -4
  42. data/lib/ruby-next/language/rewriters/base.rb +13 -1
  43. data/lib/ruby-next/language/{edge.rb → rewriters/edge.rb} +0 -0
  44. data/lib/ruby-next/language/rewriters/proposed/bind_vars_pattern.rb +50 -0
  45. data/lib/ruby-next/language/rewriters/{method_reference.rb → proposed/method_reference.rb} +0 -0
  46. data/lib/ruby-next/language/rewriters/proposed.rb +9 -0
  47. data/lib/ruby-next/language/rewriters/runtime.rb +1 -2
  48. data/lib/ruby-next/language.rb +41 -23
  49. data/lib/ruby-next/rubocop.rb +24 -4
  50. data/lib/ruby-next/version.rb +1 -1
  51. metadata +35 -23
  52. data/lib/ruby-next/language/proposed.rb +0 -9
  53. 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: 671c0ebc75d7ba834da83c55556fc9ed752fbbd6869788fc02435aef89f087b8
4
- data.tar.gz: cb17c7ca0aafa112b50431f97170cdc16caba65fdbbb2bff6ebed039f1e2e489
3
+ metadata.gz: c8f084701e3c87e432518f6367d883b9524dbc002d77352793b912504a63e8d0
4
+ data.tar.gz: bc44011f4183dd2133f259f07dc7a40c4be5dbd4cf2f85e35efb525463011ace
5
5
  SHA512:
6
- metadata.gz: 0642f3a3353f99d8ed5e76c4b530c748b7fc712c2ed04ba8e840a853087b3d94299ade88b83fd0ea472223bd5ee61f8c80618f818987ae541d4dc291eff8f0c2
7
- data.tar.gz: 326aaccb2ec27ca7a28a802f755fcfc44748a825283d82d19055afe3a84d1b42eb9a48d41d5025b9b5be33230f6f88681e16b88537770270245b0435f457d49e
6
+ metadata.gz: eabbb2bc0ccfa86f033eabce8001d208638a3995c7f112c29ed0e5f3142597bc60d87112899e7d383b09739c7c4cbe2eff06d10c58df7577cf12f80d4cb19c4d
7
+ data.tar.gz: 000c76da160d8dba1231121b7e71b335da2d9775240bfc7a9fb2201ca55d6ac366f47f1c918a3c5ebb46abdcc90857b0c3a9d221455ed6eff68965f5b8f905e9
data/CHANGELOG.md CHANGED
@@ -2,6 +2,46 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.14.1 (2022-01-21)
6
+
7
+ - Fix nested find patterns transpiling. ([@palkan][])
8
+
9
+ ## 0.14.0 🎄
10
+
11
+ - Add `Integer.try_convert`. ([@palkan][])
12
+
13
+ - Add `Enumerable#compact`, `Enumerator::Lazy#compact`. ([@palkan][])
14
+
15
+ - [Proposed] Add support for binding instance, class, global variables in pattern matching. ([@palkan][])
16
+
17
+ This brings back rightward assignment: `42 => @v`.
18
+
19
+ - Add `MatchData#match`. ([@palkan][])
20
+
21
+ - Add support for command syntax in endless methods (`def foo() = puts "bar"`)
22
+
23
+ - Add `Refinement#import_methods` support. ([@palkan][])
24
+
25
+ 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).
26
+
27
+ You can find the details in [the PR](https://github.com/ruby-next/ruby-next/pull/85).
28
+
29
+ - Added support for instance, class and global variables and expressions for pin operator. ([@palkan][])
30
+
31
+ - Support omitting parentheses in one-line pattern matching. ([@palkan][])
32
+
33
+ - Anonymous blocks `def b(&); c(&); end` ([@palkan][]).
34
+
35
+ ## 0.13.3 (2021-12-08)
36
+
37
+ - Revert 0.13.2 and freeze Parser version. ([@skryukov][])
38
+
39
+ Postpone upgrade 'till v0.14 due to breaking shorthand hash changes.
40
+
41
+ ## 0.13.2 (2021-11-28)
42
+
43
+ - Parser upgrade.
44
+
5
45
  ## 0.13.1 (2021-09-27)
6
46
 
7
47
  - Fix checking for realpath during $LOAD_PATH setup. ([@palkan][])
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
@@ -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
@@ -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