ruby-next-core 0.10.1 → 0.11.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -1
  3. data/README.md +12 -14
  4. data/bin/reparse +19 -0
  5. data/bin/transform +22 -8
  6. data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +5 -2
  7. data/lib/.rbnext/2.3/ruby-next/language/eval.rb +4 -4
  8. data/lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb +25 -10
  9. data/lib/.rbnext/2.3/ruby-next/language/rewriters/endless_range.rb +1 -1
  10. data/lib/.rbnext/2.3/ruby-next/language/rewriters/pattern_matching.rb +133 -103
  11. data/lib/.rbnext/2.3/ruby-next/utils.rb +1 -1
  12. data/lib/.rbnext/2.7/ruby-next/core.rb +203 -0
  13. data/lib/ruby-next.rb +5 -38
  14. data/lib/ruby-next/commands/nextify.rb +4 -1
  15. data/lib/ruby-next/config.rb +45 -0
  16. data/lib/ruby-next/core.rb +5 -4
  17. data/lib/ruby-next/core/array/deconstruct.rb +1 -1
  18. data/lib/ruby-next/core/constants/no_matching_pattern_error.rb +1 -1
  19. data/lib/ruby-next/core/hash/deconstruct_keys.rb +1 -1
  20. data/lib/ruby-next/core/struct/deconstruct_keys.rb +3 -3
  21. data/lib/ruby-next/core_ext.rb +2 -0
  22. data/lib/ruby-next/language.rb +16 -5
  23. data/lib/ruby-next/language/bootsnap.rb +1 -1
  24. data/lib/ruby-next/language/edge.rb +0 -6
  25. data/lib/ruby-next/language/eval.rb +3 -3
  26. data/lib/ruby-next/language/parser.rb +19 -0
  27. data/lib/ruby-next/language/rewriters/args_forward.rb +8 -4
  28. data/lib/ruby-next/language/rewriters/args_forward_leading.rb +75 -0
  29. data/lib/ruby-next/language/rewriters/base.rb +21 -6
  30. data/lib/ruby-next/language/rewriters/in_pattern.rb +56 -0
  31. data/lib/ruby-next/language/rewriters/numbered_params.rb +6 -2
  32. data/lib/ruby-next/language/rewriters/pattern_matching.rb +131 -101
  33. data/lib/ruby-next/language/rewriters/safe_navigation.rb +30 -26
  34. data/lib/ruby-next/language/setup.rb +6 -3
  35. data/lib/ruby-next/language/unparser.rb +10 -0
  36. data/lib/ruby-next/rubocop.rb +79 -12
  37. data/lib/ruby-next/setup_self.rb +2 -2
  38. data/lib/ruby-next/version.rb +1 -1
  39. metadata +15 -6
  40. data/lib/.rbnext/2.3/ruby-next/language/rewriters/right_hand_assignment.rb +0 -107
  41. data/lib/ruby-next/language/rewriters/right_hand_assignment.rb +0 -107
@@ -13,7 +13,7 @@ load_iseq = RubyVM::InstructionSequence.method(:load_iseq)
13
13
  if load_iseq.source_location[0].include?("/bootsnap/")
14
14
  Bootsnap::CompileCache::ISeq.singleton_class.prepend(
15
15
  Module.new do
16
- def input_to_storage(source, path)
16
+ def input_to_storage(source, path, *)
17
17
  return super unless RubyNext::Language.transformable?(path)
18
18
  source = RubyNext::Language.transform(source, rewriters: RubyNext::Language.current_rewriters)
19
19
 
@@ -1,9 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Load edge Ruby features
4
-
5
- require "ruby-next/language/rewriters/endless_method"
6
- RubyNext::Language.rewriters << RubyNext::Language::Rewriters::EndlessMethod
7
-
8
- require "ruby-next/language/rewriters/right_hand_assignment"
9
- RubyNext::Language.rewriters << RubyNext::Language::Rewriters::RightHandAssignment
@@ -20,7 +20,7 @@ module RubyNext
20
20
  module InstanceEval # :nodoc:
21
21
  refine Object do
22
22
  def instance_eval(*args, &block)
23
- return super(*args, &block) if block_given?
23
+ return super(*args, &block) if block
24
24
 
25
25
  source = args.shift
26
26
  new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
@@ -33,7 +33,7 @@ module RubyNext
33
33
  module ClassEval
34
34
  refine Module do
35
35
  def module_eval(*args, &block)
36
- return super(*args, &block) if block_given?
36
+ return super(*args, &block) if block
37
37
 
38
38
  source = args.shift
39
39
  new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
@@ -42,7 +42,7 @@ module RubyNext
42
42
  end
43
43
 
44
44
  def class_eval(*args, &block)
45
- return super(*args, &block) if block_given?
45
+ return super(*args, &block) if block
46
46
 
47
47
  source = args.shift
48
48
  new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
@@ -4,8 +4,27 @@ require "parser/rubynext"
4
4
 
5
5
  module RubyNext
6
6
  module Language
7
+ module BuilderExt
8
+ def match_pattern(lhs, match_t, rhs)
9
+ n(:match_pattern, [lhs, rhs],
10
+ binary_op_map(lhs, match_t, rhs))
11
+ end
12
+
13
+ def match_pattern_p(lhs, match_t, rhs)
14
+ n(:match_pattern_p, [lhs, rhs],
15
+ binary_op_map(lhs, match_t, rhs))
16
+ end
17
+ end
18
+
7
19
  class Builder < ::Parser::Builders::Default
8
20
  modernize
21
+
22
+ # Unparser doesn't support kwargs node yet
23
+ self.emit_kwargs = false if respond_to?(:emit_kwargs=)
24
+
25
+ unless method_defined?(:match_pattern_p)
26
+ include BuilderExt
27
+ end
9
28
  end
10
29
 
11
30
  class << self
@@ -5,8 +5,8 @@ module RubyNext
5
5
  module Rewriters
6
6
  class ArgsForward < Base
7
7
  NAME = "args-forward"
8
- SYNTAX_PROBE = "obj = Object.new; def obj.foo(...) super(1, ...); end"
9
- MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.2")
8
+ SYNTAX_PROBE = "obj = Object.new; def obj.foo(...) super(...); end"
9
+ MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
10
10
 
11
11
  REST = :__rest__
12
12
  BLOCK = :__block__
@@ -28,14 +28,14 @@ module RubyNext
28
28
  end
29
29
 
30
30
  def on_send(node)
31
- fargs = node.children.find { |child| child.is_a?(::Parser::AST::Node) && child.type == :forwarded_args }
31
+ fargs = extract_fargs(node)
32
32
  return super(node) unless fargs
33
33
 
34
34
  process_fargs(node, fargs)
35
35
  end
36
36
 
37
37
  def on_super(node)
38
- fargs = node.children.find { |child| child.is_a?(::Parser::AST::Node) && child.type == :forwarded_args }
38
+ fargs = extract_fargs(node)
39
39
  return super(node) unless fargs
40
40
 
41
41
  process_fargs(node, fargs)
@@ -43,6 +43,10 @@ module RubyNext
43
43
 
44
44
  private
45
45
 
46
+ def extract_fargs(node)
47
+ node.children.find { |child| child.is_a?(::Parser::AST::Node) && child.type == :forwarded_args }
48
+ end
49
+
46
50
  def process_fargs(node, fargs)
47
51
  replace(fargs.loc.expression, "*#{REST}, &#{BLOCK}")
48
52
 
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ class ArgsForwardLeading < ArgsForward
7
+ NAME = "args-forward-leading"
8
+ SYNTAX_PROBE = "obj = Object.new; def obj.foo(...) super(1, ...); end"
9
+ MIN_SUPPORTED_VERSION = Gem::Version.new("3.0.0")
10
+
11
+ attr_reader :leading_farg
12
+ alias leading_farg? leading_farg
13
+
14
+ def on_def(node)
15
+ @leading_farg = method_with_leading_arg(node)
16
+
17
+ super
18
+ end
19
+
20
+ def on_defs(node)
21
+ @leading_farg = method_with_leading_arg(node)
22
+
23
+ super
24
+ end
25
+
26
+ def on_forward_arg(node)
27
+ return super if leading_farg?
28
+
29
+ node
30
+ end
31
+
32
+ def on_send(node)
33
+ return super if leading_farg?
34
+
35
+ node
36
+ end
37
+
38
+ def on_super(node)
39
+ return super if leading_farg?
40
+
41
+ node
42
+ end
43
+
44
+ private
45
+
46
+ def send_with_leading_farg(node)
47
+ return false unless node.type == :send || node.type == :super
48
+
49
+ fargs = extract_fargs(node)
50
+
51
+ return false unless fargs
52
+
53
+ node.children.index(fargs) > (node.type == :send ? 2 : 0)
54
+ end
55
+
56
+ def method_with_leading_arg(node)
57
+ find_child(node) { |child| child.type == :forward_arg } &&
58
+ (
59
+ def_with_leading_farg(node) ||
60
+ find_child(node) { |child| send_with_leading_farg(child) }
61
+ )
62
+ end
63
+
64
+ def def_with_leading_farg(node)
65
+ args = node.type == :defs ? node.children[2] : node.children[1]
66
+ args = args.children
67
+
68
+ farg = args.detect { |child| child.type == :forward_arg }
69
+
70
+ args.index(farg) > 0
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -61,7 +61,7 @@ module RubyNext
61
61
  eval_mid = Kernel.respond_to?(:eval_without_ruby_next) ? :eval_without_ruby_next : :eval
62
62
  Kernel.send eval_mid, self::SYNTAX_PROBE, nil, __FILE__, __LINE__
63
63
  false
64
- rescue SyntaxError, NameError
64
+ rescue SyntaxError, StandardError
65
65
  true
66
66
  ensure
67
67
  $VERBOSE = save_verbose
@@ -73,10 +73,6 @@ module RubyNext
73
73
  self::MIN_SUPPORTED_VERSION > version
74
74
  end
75
75
 
76
- def min_supported_minor_version
77
- Gem::Version.new(self::MIN_SUPPORTED_VERSION.segments[0..1].join(".") + ".0")
78
- end
79
-
80
76
  private
81
77
 
82
78
  def transform(source)
@@ -98,6 +94,24 @@ module RubyNext
98
94
 
99
95
  private
100
96
 
97
+ # BFS with predicate block
98
+ def find_child(node)
99
+ queue = [node]
100
+
101
+ loop do
102
+ break if queue.empty?
103
+
104
+ child = queue.shift
105
+ next unless child.is_a?(::Parser::AST::Node)
106
+
107
+ return child if yield child
108
+
109
+ queue.push(*child.children)
110
+ end
111
+
112
+ nil
113
+ end
114
+
101
115
  def replace(range, ast)
102
116
  @source_rewriter&.replace(range, unparse(ast))
103
117
  end
@@ -116,7 +130,8 @@ module RubyNext
116
130
 
117
131
  def unparse(ast)
118
132
  return ast if ast.is_a?(String)
119
- Unparser.unparse(ast)
133
+
134
+ Unparser.unparse(ast).chomp
120
135
  end
121
136
 
122
137
  attr_reader :context
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby-next/language/rewriters/pattern_matching"
4
+
5
+ module RubyNext
6
+ module Language
7
+ module Rewriters
8
+ using RubyNext
9
+
10
+ # Separate pattern matching rewriter for Ruby 2.7 to
11
+ # transpile only `in` patterns
12
+ class InPattern < PatternMatching
13
+ NAME = "pattern-matching-in"
14
+ SYNTAX_PROBE = "1 in 2"
15
+ MIN_SUPPORTED_VERSION = Gem::Version.new("3.0.0")
16
+
17
+ # Make case-match no-op
18
+ def on_case_match(node)
19
+ node
20
+ end
21
+
22
+ def on_match_pattern_p(node)
23
+ context.track! self
24
+
25
+ @deconstructed_keys = {}
26
+ @predicates = Predicates::Noop.new
27
+
28
+ matchee =
29
+ s(:begin, s(:lvasgn, MATCHEE, node.children[0]))
30
+
31
+ pattern =
32
+ locals.with(
33
+ matchee: MATCHEE,
34
+ arr: MATCHEE_ARR,
35
+ hash: MATCHEE_HASH
36
+ ) do
37
+ send(
38
+ :"#{node.children[1].type}_clause",
39
+ node.children[1]
40
+ )
41
+ end
42
+
43
+ node.updated(
44
+ :and,
45
+ [
46
+ matchee,
47
+ pattern
48
+ ]
49
+ ).tap do |new_node|
50
+ replace(node.loc.expression, inline_blocks(unparse(new_node)))
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -16,9 +16,9 @@ module RubyNext
16
16
  proc_or_lambda, num, body = *node.children
17
17
 
18
18
  if proc_or_lambda.type == :lambda
19
- insert_before(node.loc.begin, "(#{unparse(proc_args(num))})")
19
+ insert_before(node.loc.begin, "(#{proc_args_str(num)})")
20
20
  else
21
- insert_after(node.loc.begin, " |#{unparse(proc_args(num))}|")
21
+ insert_after(node.loc.begin, " |#{proc_args_str(num)}|")
22
22
  end
23
23
 
24
24
  node.updated(
@@ -33,6 +33,10 @@ module RubyNext
33
33
 
34
34
  private
35
35
 
36
+ def proc_args_str(n)
37
+ (1..n).map { |numero| "_#{numero}" }.join(", ")
38
+ end
39
+
36
40
  def proc_args(n)
37
41
  return s(:args, s(:procarg0, s(:arg, :_1))) if n == 1
38
42
 
@@ -13,12 +13,12 @@ module RubyNext
13
13
 
14
14
  # Useful to generate simple operation nodes
15
15
  # (e.g., 'a + b')
16
- def -(val)
17
- ::Parser::AST::Node.new(:send, [self, :-, val.to_ast_node])
16
+ def -(other)
17
+ ::Parser::AST::Node.new(:send, [self, :-, other.to_ast_node])
18
18
  end
19
19
 
20
- def +(val)
21
- ::Parser::AST::Node.new(:send, [self, :+, val.to_ast_node])
20
+ def +(other)
21
+ ::Parser::AST::Node.new(:send, [self, :+, other.to_ast_node])
22
22
  end
23
23
  end
24
24
 
@@ -218,9 +218,10 @@ module RubyNext
218
218
  predicate_clause(:respond_to_deconstruct_keys, node)
219
219
  end
220
220
 
221
- def hash_key(node, key)
222
- key = key.children.first if key.is_a?(::Parser::AST::Node)
223
- predicate_clause(:"hash_key_#{key}", node)
221
+ def hash_keys(node, keys)
222
+ keys = keys.map { |key| key.is_a?(::Parser::AST::Node) ? key.children.first : key }
223
+
224
+ predicate_clause(:"hash_keys_#{keys.join("_p_")}", node)
224
225
  end
225
226
  end
226
227
  end
@@ -244,7 +245,7 @@ module RubyNext
244
245
  @predicates = Predicates::CaseIn.new
245
246
 
246
247
  matchee_ast =
247
- s(:lvasgn, MATCHEE, node.children[0])
248
+ s(:begin, s(:lvasgn, MATCHEE, node.children[0]))
248
249
 
249
250
  patterns = locals.with(
250
251
  matchee: MATCHEE,
@@ -266,14 +267,14 @@ module RubyNext
266
267
  )
267
268
  end
268
269
 
269
- def on_in_match(node)
270
+ def on_match_pattern(node)
270
271
  context.track! self
271
272
 
272
273
  @deconstructed_keys = {}
273
274
  @predicates = Predicates::Noop.new
274
275
 
275
276
  matchee =
276
- s(:lvasgn, MATCHEE, node.children[0])
277
+ s(:begin, s(:lvasgn, MATCHEE, node.children[0]))
277
278
 
278
279
  pattern =
279
280
  locals.with(
@@ -285,9 +286,10 @@ module RubyNext
285
286
  :"#{node.children[1].type}_clause",
286
287
  node.children[1]
287
288
  ).then do |node|
288
- s(:or,
289
- node,
290
- no_matching_pattern)
289
+ s(:begin,
290
+ s(:or,
291
+ node,
292
+ no_matching_pattern))
291
293
  end
292
294
  end
293
295
 
@@ -302,6 +304,8 @@ module RubyNext
302
304
  end
303
305
  end
304
306
 
307
+ alias on_in_match on_match_pattern
308
+
305
309
  private
306
310
 
307
311
  def rewrite_case_in!(node, matchee, new_node)
@@ -378,9 +382,10 @@ module RubyNext
378
382
  predicates.const(case_eq_clause(const, right), const).then do |node|
379
383
  next node if pattern.nil?
380
384
 
381
- s(:and,
382
- node,
383
- send(:"#{pattern.type}_clause", pattern))
385
+ s(:begin,
386
+ s(:and,
387
+ node,
388
+ send(:"#{pattern.type}_clause", pattern)))
384
389
  end
385
390
  end
386
391
 
@@ -391,13 +396,14 @@ module RubyNext
391
396
  send :"#{child.type}_clause", child
392
397
  end
393
398
  end
394
- s(:or, *children)
399
+ s(:begin, s(:or, *children))
395
400
  end
396
401
 
397
402
  def match_as_clause(node, right = s(:lvar, locals[:matchee]))
398
- s(:and,
399
- send(:"#{node.children[0].type}_clause", node.children[0], right),
400
- match_var_clause(node.children[1], right))
403
+ s(:begin,
404
+ s(:and,
405
+ send(:"#{node.children[0].type}_clause", node.children[0], right),
406
+ match_var_clause(node.children[1], right)))
401
407
  end
402
408
 
403
409
  def match_var_clause(node, left = s(:lvar, locals[:matchee]))
@@ -405,9 +411,10 @@ module RubyNext
405
411
 
406
412
  check_match_var_alternation! node.children[0]
407
413
 
408
- s(:or,
409
- s(:lvasgn, node.children[0], left),
410
- s(:true))
414
+ s(:begin,
415
+ s(:or,
416
+ s(:begin, s(:lvasgn, node.children[0], left)),
417
+ s(:true)))
411
418
  end
412
419
 
413
420
  def pin_clause(node, right = s(:lvar, locals[:matchee]))
@@ -417,8 +424,8 @@ module RubyNext
417
424
 
418
425
  def case_eq_clause(node, right = s(:lvar, locals[:matchee]))
419
426
  predicates.terminate!
420
- s(:send,
421
- process(node), :===, right)
427
+ s(:begin, s(:send,
428
+ process(node), :===, right))
422
429
  end
423
430
 
424
431
  #=========== ARRAY PATTERN (START) ===============
@@ -429,10 +436,11 @@ module RubyNext
429
436
  # if there is no rest or tail, match the size first
430
437
  unless node.type == :array_pattern_with_tail || node.children.any? { |n| n.type == :match_rest }
431
438
  size_check = predicates.array_size(
432
- s(:send,
433
- node.children.size.to_ast_node,
434
- :==,
435
- s(:send, s(:lvar, locals[:arr]), :size)),
439
+ s(:begin,
440
+ s(:send,
441
+ node.children.size.to_ast_node,
442
+ :==,
443
+ s(:send, s(:lvar, locals[:arr]), :size))),
436
444
  node.children.size
437
445
  )
438
446
  end
@@ -448,9 +456,10 @@ module RubyNext
448
456
 
449
457
  right = s(:and, size_check, right) if size_check
450
458
 
451
- s(:and,
452
- dnode,
453
- right)
459
+ s(:begin,
460
+ s(:and,
461
+ dnode,
462
+ right))
454
463
  end
455
464
  end
456
465
 
@@ -468,14 +477,17 @@ module RubyNext
468
477
  predicates.array_deconstructed(
469
478
  s(:and,
470
479
  respond_check,
471
- s(:and,
472
- s(:or,
473
- s(:lvasgn, locals[:arr], right),
474
- s(:true)),
475
- s(:or,
476
- s(:send,
477
- s(:const, nil, :Array), :===, s(:lvar, locals[:arr])),
478
- raise_error(:TypeError, "#deconstruct must return Array"))))
480
+ s(:begin,
481
+ s(:and,
482
+ s(:begin,
483
+ s(:or,
484
+ s(:begin, s(:lvasgn, locals[:arr], right)),
485
+ s(:true))),
486
+ s(:begin,
487
+ s(:or,
488
+ s(:send,
489
+ s(:const, nil, :Array), :===, s(:lvar, locals[:arr])),
490
+ raise_error(:TypeError, "#deconstruct must return Array"))))))
479
491
  )
480
492
  end
481
493
 
@@ -485,9 +497,10 @@ module RubyNext
485
497
  send("#{head.type}_array_element", head, index).then do |node|
486
498
  next node if tail.empty?
487
499
 
488
- s(:and,
489
- node,
490
- array_element(index + 1, *tail))
500
+ s(:begin,
501
+ s(:and,
502
+ node,
503
+ array_element(index + 1, *tail)))
491
504
  end
492
505
  end
493
506
 
@@ -526,15 +539,17 @@ module RubyNext
526
539
 
527
540
  pattern = array_rest_element(*nodes, index).then do |needle|
528
541
  next needle unless head_match
529
- s(:and,
530
- needle,
531
- head_match)
542
+ s(:begin,
543
+ s(:and,
544
+ needle,
545
+ head_match))
532
546
  end.then do |headed_needle|
533
547
  next headed_needle unless tail_match
534
548
 
535
- s(:and,
536
- headed_needle,
537
- tail_match)
549
+ s(:begin,
550
+ s(:and,
551
+ headed_needle,
552
+ tail_match))
538
553
  end
539
554
 
540
555
  s(:block,
@@ -550,13 +565,14 @@ module RubyNext
550
565
  next block if match_vars.empty?
551
566
 
552
567
  # We need to declare match vars outside of `find` block
553
- locals_declare = s(:masgn,
568
+ locals_declare = s(:begin, s(:masgn,
554
569
  s(:mlhs, *match_vars),
555
- s(:nil))
570
+ s(:nil)))
556
571
 
557
- s(:or,
558
- locals_declare,
559
- block)
572
+ s(:begin,
573
+ s(:or,
574
+ locals_declare,
575
+ block))
560
576
  end
561
577
  end
562
578
 
@@ -575,18 +591,20 @@ module RubyNext
575
591
 
576
592
  return rest if tail.empty?
577
593
 
578
- s(:and,
579
- rest,
580
- array_rest_element(*tail, -(size - 1)))
594
+ s(:begin,
595
+ s(:and,
596
+ rest,
597
+ array_rest_element(*tail, -(size - 1))))
581
598
  end
582
599
 
583
600
  def array_rest_element(head, *tail, index)
584
601
  send("#{head.type}_array_element", head, index).then do |node|
585
602
  next node if tail.empty?
586
603
 
587
- s(:and,
588
- node,
589
- array_rest_element(*tail, index + 1))
604
+ s(:begin,
605
+ s(:and,
606
+ node,
607
+ array_rest_element(*tail, index + 1)))
590
608
  end
591
609
  end
592
610
 
@@ -610,7 +628,7 @@ module RubyNext
610
628
  children = node.children.map do |child, i|
611
629
  send :"#{child.type}_array_element", child, index
612
630
  end
613
- s(:or, *children)
631
+ s(:begin, s(:or, *children))
614
632
  end
615
633
 
616
634
  def match_var_array_element(node, index)
@@ -661,18 +679,20 @@ module RubyNext
661
679
  elsif specified_key_names.empty?
662
680
  hash_element(*node.children)
663
681
  else
664
- s(:and,
665
- having_hash_keys(specified_key_names),
666
- hash_element(*node.children))
682
+ s(:begin,
683
+ s(:and,
684
+ having_hash_keys(specified_key_names),
685
+ hash_element(*node.children)))
667
686
  end
668
687
 
669
688
  predicates.pop
670
689
 
671
690
  next dnode if right.nil?
672
691
 
673
- s(:and,
674
- dnode,
675
- right)
692
+ s(:begin,
693
+ s(:and,
694
+ dnode,
695
+ right))
676
696
  end
677
697
  end
678
698
 
@@ -715,7 +735,7 @@ module RubyNext
715
735
  # Duplicate the source hash when matching **rest, 'cause we mutate it
716
736
  hash_dup =
717
737
  if @hash_match_rest
718
- s(:lvasgn, locals[:hash], s(:send, s(:lvar, locals[:hash, :src]), :dup))
738
+ s(:begin, s(:lvasgn, locals[:hash], s(:send, s(:lvar, locals[:hash, :src]), :dup)))
719
739
  else
720
740
  s(:true)
721
741
  end
@@ -728,29 +748,33 @@ module RubyNext
728
748
  key_names = keys.children.map { |node| node.children.last }
729
749
  predicates.push locals[:hash]
730
750
 
731
- s(:lvasgn, deconstruct_name,
751
+ s(:begin, s(:lvasgn, deconstruct_name,
732
752
  s(:send,
733
- matchee, :deconstruct_keys, keys)).then do |dnode|
753
+ matchee, :deconstruct_keys, keys))).then do |dnode|
734
754
  next dnode if respond_to_checked
735
755
 
736
756
  s(:and,
737
757
  respond_check,
738
- s(:and,
739
- s(:or,
740
- dnode,
741
- s(:true)),
742
- s(:or,
743
- s(:send,
744
- s(:const, nil, :Hash), :===, s(:lvar, deconstruct_name)),
745
- raise_error(:TypeError, "#deconstruct_keys must return Hash"))))
758
+ s(:begin,
759
+ s(:and,
760
+ s(:begin,
761
+ s(:or,
762
+ dnode,
763
+ s(:true))),
764
+ s(:begin,
765
+ s(:or,
766
+ s(:send,
767
+ s(:const, nil, :Hash), :===, s(:lvar, deconstruct_name)),
768
+ raise_error(:TypeError, "#deconstruct_keys must return Hash"))))))
746
769
  end.then do |dnode|
747
770
  predicates.hash_deconstructed(dnode, key_names)
748
771
  end.then do |dnode|
749
772
  next dnode unless @hash_match_rest
750
773
 
751
- s(:and,
752
- dnode,
753
- hash_dup)
774
+ s(:begin,
775
+ s(:and,
776
+ dnode,
777
+ hash_dup))
754
778
  end
755
779
  end
756
780
 
@@ -780,9 +804,10 @@ module RubyNext
780
804
 
781
805
  next node if right.nil?
782
806
 
783
- s(:and,
784
- node,
785
- right)
807
+ s(:begin,
808
+ s(:and,
809
+ node,
810
+ right))
786
811
  end
787
812
  end
788
813
 
@@ -792,7 +817,7 @@ module RubyNext
792
817
  end
793
818
 
794
819
  def match_alt_hash_element(node, key)
795
- element_node = s(:lvasgn, locals[:hash, :el], hash_value_at(key))
820
+ element_node = s(:begin, s(:lvasgn, locals[:hash, :el], hash_value_at(key)))
796
821
 
797
822
  children = locals.with(hash_element: locals[:hash, :el]) do
798
823
  node.children.map do |child, i|
@@ -800,11 +825,14 @@ module RubyNext
800
825
  end
801
826
  end
802
827
 
803
- s(:and,
804
- s(:or,
805
- element_node,
806
- s(:true)),
807
- s(:or, *children))
828
+ s(:begin,
829
+ s(:and,
830
+ s(:begin,
831
+ s(:or,
832
+ element_node,
833
+ s(:true))),
834
+ s(:begin,
835
+ s(:or, *children))))
808
836
  end
809
837
 
810
838
  def match_as_hash_element(node, key)
@@ -858,13 +886,14 @@ module RubyNext
858
886
  end
859
887
 
860
888
  def having_hash_keys(keys, hash = s(:lvar, locals[:hash]))
861
- key = keys.shift
862
- node = predicates.hash_key(hash_has_key(key, hash), key)
889
+ keys.reduce(nil) do |acc, key|
890
+ pnode = hash_has_key(key, hash)
891
+ next pnode unless acc
863
892
 
864
- keys.reduce(node) do |res, key|
865
- s(:and,
866
- res,
867
- predicates.hash_key(hash_has_key(key, hash), key))
893
+ s(:begin,
894
+ s(:and, acc, pnode))
895
+ end.then do |node|
896
+ predicates.hash_keys(node, keys)
868
897
  end
869
898
  end
870
899
 
@@ -873,9 +902,10 @@ module RubyNext
873
902
  def with_guard(node, guard)
874
903
  return node unless guard
875
904
 
876
- s(:and,
877
- node,
878
- guard.children[0]).then do |expr|
905
+ s(:begin,
906
+ s(:and,
907
+ node,
908
+ guard.children[0])).then do |expr|
879
909
  next expr unless guard.type == :unless_guard
880
910
  s(:send, expr, :!)
881
911
  end
@@ -933,10 +963,10 @@ module RubyNext
933
963
  deconstructed_keys[key] = :"k#{deconstructed_keys.size}"
934
964
  end
935
965
 
936
- # Unparser generates `do .. end` blocks, we want to
966
+ # Unparser generates `do .. end` or `{ ... }` multiline blocks, we want to
937
967
  # have single-line blocks with `{ ... }`.
938
968
  def inline_blocks(source)
939
- source.gsub(/do \|_, __i__\|\n\s*([^\n]+)\n\s*end/, '{ |_, __i__| \1 }')
969
+ source.gsub(/(?:do|{) \|_, __i__\|\n\s*([^\n]+)\n\s*(?:end|})/, '{ |_, __i__| \1 }')
940
970
  end
941
971
  end
942
972
  end