prism 0.28.0 → 0.30.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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -1
  3. data/CONTRIBUTING.md +0 -4
  4. data/README.md +1 -0
  5. data/config.yml +95 -26
  6. data/docs/fuzzing.md +1 -1
  7. data/docs/ripper_translation.md +22 -0
  8. data/ext/prism/api_node.c +70 -52
  9. data/ext/prism/extconf.rb +27 -23
  10. data/ext/prism/extension.c +107 -372
  11. data/ext/prism/extension.h +1 -1
  12. data/include/prism/ast.h +170 -102
  13. data/include/prism/diagnostic.h +18 -3
  14. data/include/prism/node.h +0 -21
  15. data/include/prism/parser.h +23 -25
  16. data/include/prism/regexp.h +17 -8
  17. data/include/prism/static_literals.h +3 -2
  18. data/include/prism/util/pm_char.h +1 -2
  19. data/include/prism/util/pm_constant_pool.h +0 -8
  20. data/include/prism/util/pm_integer.h +16 -9
  21. data/include/prism/util/pm_string.h +0 -8
  22. data/include/prism/version.h +2 -2
  23. data/include/prism.h +0 -11
  24. data/lib/prism/compiler.rb +3 -0
  25. data/lib/prism/desugar_compiler.rb +4 -4
  26. data/lib/prism/dispatcher.rb +14 -0
  27. data/lib/prism/dot_visitor.rb +54 -35
  28. data/lib/prism/dsl.rb +23 -18
  29. data/lib/prism/ffi.rb +25 -4
  30. data/lib/prism/inspect_visitor.rb +26 -24
  31. data/lib/prism/mutation_compiler.rb +6 -1
  32. data/lib/prism/node.rb +314 -389
  33. data/lib/prism/node_ext.rb +175 -17
  34. data/lib/prism/parse_result/comments.rb +1 -8
  35. data/lib/prism/parse_result/newlines.rb +102 -12
  36. data/lib/prism/parse_result.rb +17 -0
  37. data/lib/prism/reflection.rb +11 -9
  38. data/lib/prism/serialize.rb +91 -68
  39. data/lib/prism/translation/parser/compiler.rb +288 -138
  40. data/lib/prism/translation/parser.rb +7 -2
  41. data/lib/prism/translation/ripper.rb +24 -22
  42. data/lib/prism/translation/ruby_parser.rb +32 -14
  43. data/lib/prism/visitor.rb +3 -0
  44. data/lib/prism.rb +0 -4
  45. data/prism.gemspec +2 -4
  46. data/rbi/prism/node.rbi +114 -57
  47. data/rbi/prism/node_ext.rbi +5 -0
  48. data/rbi/prism/parse_result.rbi +1 -1
  49. data/rbi/prism/visitor.rbi +3 -0
  50. data/rbi/prism.rbi +6 -0
  51. data/sig/prism/dsl.rbs +13 -10
  52. data/sig/prism/lex_compat.rbs +10 -0
  53. data/sig/prism/mutation_compiler.rbs +1 -0
  54. data/sig/prism/node.rbs +72 -48
  55. data/sig/prism/node_ext.rbs +4 -0
  56. data/sig/prism/visitor.rbs +1 -0
  57. data/sig/prism.rbs +21 -0
  58. data/src/diagnostic.c +56 -27
  59. data/src/node.c +432 -1690
  60. data/src/prettyprint.c +97 -54
  61. data/src/prism.c +1286 -1196
  62. data/src/regexp.c +133 -68
  63. data/src/serialize.c +22 -17
  64. data/src/static_literals.c +63 -84
  65. data/src/token_type.c +4 -4
  66. data/src/util/pm_constant_pool.c +0 -8
  67. data/src/util/pm_integer.c +39 -11
  68. data/src/util/pm_string.c +0 -12
  69. data/src/util/pm_strpbrk.c +32 -6
  70. metadata +3 -5
  71. data/include/prism/util/pm_string_list.h +0 -44
  72. data/lib/prism/debug.rb +0 -249
  73. data/src/util/pm_string_list.c +0 -28
@@ -90,7 +90,11 @@ module Prism
90
90
  end
91
91
 
92
92
  if node.constant
93
- builder.const_pattern(visit(node.constant), token(node.opening_loc), builder.array_pattern(nil, visited, nil), token(node.closing_loc))
93
+ if visited.empty?
94
+ builder.const_pattern(visit(node.constant), token(node.opening_loc), builder.array_pattern(token(node.opening_loc), visited, token(node.closing_loc)), token(node.closing_loc))
95
+ else
96
+ builder.const_pattern(visit(node.constant), token(node.opening_loc), builder.array_pattern(nil, visited, nil), token(node.closing_loc))
97
+ end
94
98
  else
95
99
  builder.array_pattern(token(node.opening_loc), visited, token(node.closing_loc))
96
100
  end
@@ -105,38 +109,46 @@ module Prism
105
109
  # { a: 1 }
106
110
  # ^^^^
107
111
  def visit_assoc_node(node)
112
+ key = node.key
113
+
108
114
  if in_pattern
109
115
  if node.value.is_a?(ImplicitNode)
110
- if node.key.is_a?(SymbolNode)
111
- builder.match_hash_var([node.key.unescaped, srange(node.key.location)])
116
+ if key.is_a?(SymbolNode)
117
+ if key.opening.nil?
118
+ builder.match_hash_var([key.unescaped, srange(key.location)])
119
+ else
120
+ builder.match_hash_var_from_str(token(key.opening_loc), [builder.string_internal([key.unescaped, srange(key.value_loc)])], token(key.closing_loc))
121
+ end
112
122
  else
113
- builder.match_hash_var_from_str(token(node.key.opening_loc), visit_all(node.key.parts), token(node.key.closing_loc))
123
+ builder.match_hash_var_from_str(token(key.opening_loc), visit_all(key.parts), token(key.closing_loc))
114
124
  end
125
+ elsif key.opening.nil?
126
+ builder.pair_keyword([key.unescaped, srange(key.location)], visit(node.value))
115
127
  else
116
- builder.pair_keyword([node.key.unescaped, srange(node.key.location)], visit(node.value))
128
+ builder.pair_quoted(token(key.opening_loc), [builder.string_internal([key.unescaped, srange(key.value_loc)])], token(key.closing_loc), visit(node.value))
117
129
  end
118
130
  elsif node.value.is_a?(ImplicitNode)
119
131
  if (value = node.value.value).is_a?(LocalVariableReadNode)
120
132
  builder.pair_keyword(
121
- [node.key.unescaped, srange(node.key)],
122
- builder.ident([value.name, srange(node.key.value_loc)]).updated(:lvar)
133
+ [key.unescaped, srange(key)],
134
+ builder.ident([value.name, srange(key.value_loc)]).updated(:lvar)
123
135
  )
124
136
  else
125
- builder.pair_label([node.key.unescaped, srange(node.key.location)])
137
+ builder.pair_label([key.unescaped, srange(key.location)])
126
138
  end
127
139
  elsif node.operator_loc
128
- builder.pair(visit(node.key), token(node.operator_loc), visit(node.value))
129
- elsif node.key.is_a?(SymbolNode) && node.key.opening_loc.nil?
130
- builder.pair_keyword([node.key.unescaped, srange(node.key.location)], visit(node.value))
140
+ builder.pair(visit(key), token(node.operator_loc), visit(node.value))
141
+ elsif key.is_a?(SymbolNode) && key.opening_loc.nil?
142
+ builder.pair_keyword([key.unescaped, srange(key.location)], visit(node.value))
131
143
  else
132
144
  parts =
133
- if node.key.is_a?(SymbolNode)
134
- [builder.string_internal([node.key.unescaped, srange(node.key.value_loc)])]
145
+ if key.is_a?(SymbolNode)
146
+ [builder.string_internal([key.unescaped, srange(key.value_loc)])]
135
147
  else
136
- visit_all(node.key.parts)
148
+ visit_all(key.parts)
137
149
  end
138
150
 
139
- builder.pair_quoted(token(node.key.opening_loc), parts, token(node.key.closing_loc), visit(node.value))
151
+ builder.pair_quoted(token(key.opening_loc), parts, token(key.closing_loc), visit(node.value))
140
152
  end
141
153
  end
142
154
 
@@ -146,7 +158,9 @@ module Prism
146
158
  # { **foo }
147
159
  # ^^^^^
148
160
  def visit_assoc_splat_node(node)
149
- if node.value.nil? && forwarding.include?(:**)
161
+ if in_pattern
162
+ builder.match_rest(token(node.operator_loc), token(node.value&.location))
163
+ elsif node.value.nil? && forwarding.include?(:**)
150
164
  builder.forwarded_kwrestarg(token(node.operator_loc))
151
165
  else
152
166
  builder.kwsplat(token(node.operator_loc), visit(node.value))
@@ -328,18 +342,48 @@ module Prism
328
342
  [],
329
343
  nil
330
344
  ),
331
- [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
345
+ [node.binary_operator_loc.slice.chomp("="), srange(node.binary_operator_loc)],
332
346
  visit(node.value)
333
347
  )
334
348
  end
335
349
 
336
350
  # foo.bar &&= baz
337
351
  # ^^^^^^^^^^^^^^^
338
- alias visit_call_and_write_node visit_call_operator_write_node
352
+ def visit_call_and_write_node(node)
353
+ call_operator_loc = node.call_operator_loc
354
+
355
+ builder.op_assign(
356
+ builder.call_method(
357
+ visit(node.receiver),
358
+ call_operator_loc.nil? ? nil : [{ "." => :dot, "&." => :anddot, "::" => "::" }.fetch(call_operator_loc.slice), srange(call_operator_loc)],
359
+ node.message_loc ? [node.read_name, srange(node.message_loc)] : nil,
360
+ nil,
361
+ [],
362
+ nil
363
+ ),
364
+ [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
365
+ visit(node.value)
366
+ )
367
+ end
339
368
 
340
369
  # foo.bar ||= baz
341
370
  # ^^^^^^^^^^^^^^^
342
- alias visit_call_or_write_node visit_call_operator_write_node
371
+ def visit_call_or_write_node(node)
372
+ call_operator_loc = node.call_operator_loc
373
+
374
+ builder.op_assign(
375
+ builder.call_method(
376
+ visit(node.receiver),
377
+ call_operator_loc.nil? ? nil : [{ "." => :dot, "&." => :anddot, "::" => "::" }.fetch(call_operator_loc.slice), srange(call_operator_loc)],
378
+ node.message_loc ? [node.read_name, srange(node.message_loc)] : nil,
379
+ nil,
380
+ [],
381
+ nil
382
+ ),
383
+ [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
384
+ visit(node.value)
385
+ )
386
+ end
343
387
 
344
388
  # foo.bar, = 1
345
389
  # ^^^^^^^
@@ -419,18 +463,30 @@ module Prism
419
463
  def visit_class_variable_operator_write_node(node)
420
464
  builder.op_assign(
421
465
  builder.assignable(builder.cvar(token(node.name_loc))),
422
- [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
466
+ [node.binary_operator_loc.slice.chomp("="), srange(node.binary_operator_loc)],
423
467
  visit(node.value)
424
468
  )
425
469
  end
426
470
 
427
471
  # @@foo &&= bar
428
472
  # ^^^^^^^^^^^^^
429
- alias visit_class_variable_and_write_node visit_class_variable_operator_write_node
473
+ def visit_class_variable_and_write_node(node)
474
+ builder.op_assign(
475
+ builder.assignable(builder.cvar(token(node.name_loc))),
476
+ [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
477
+ visit(node.value)
478
+ )
479
+ end
430
480
 
431
481
  # @@foo ||= bar
432
482
  # ^^^^^^^^^^^^^
433
- alias visit_class_variable_or_write_node visit_class_variable_operator_write_node
483
+ def visit_class_variable_or_write_node(node)
484
+ builder.op_assign(
485
+ builder.assignable(builder.cvar(token(node.name_loc))),
486
+ [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
487
+ visit(node.value)
488
+ )
489
+ end
434
490
 
435
491
  # @@foo, = bar
436
492
  # ^^^^^
@@ -458,18 +514,30 @@ module Prism
458
514
  def visit_constant_operator_write_node(node)
459
515
  builder.op_assign(
460
516
  builder.assignable(builder.const([node.name, srange(node.name_loc)])),
461
- [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
517
+ [node.binary_operator_loc.slice.chomp("="), srange(node.binary_operator_loc)],
462
518
  visit(node.value)
463
519
  )
464
520
  end
465
521
 
466
522
  # Foo &&= bar
467
523
  # ^^^^^^^^^^^^
468
- alias visit_constant_and_write_node visit_constant_operator_write_node
524
+ def visit_constant_and_write_node(node)
525
+ builder.op_assign(
526
+ builder.assignable(builder.const([node.name, srange(node.name_loc)])),
527
+ [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
528
+ visit(node.value)
529
+ )
530
+ end
469
531
 
470
532
  # Foo ||= bar
471
533
  # ^^^^^^^^^^^^
472
- alias visit_constant_or_write_node visit_constant_operator_write_node
534
+ def visit_constant_or_write_node(node)
535
+ builder.op_assign(
536
+ builder.assignable(builder.const([node.name, srange(node.name_loc)])),
537
+ [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
538
+ visit(node.value)
539
+ )
540
+ end
473
541
 
474
542
  # Foo, = bar
475
543
  # ^^^
@@ -512,18 +580,30 @@ module Prism
512
580
  def visit_constant_path_operator_write_node(node)
513
581
  builder.op_assign(
514
582
  builder.assignable(visit(node.target)),
515
- [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
583
+ [node.binary_operator_loc.slice.chomp("="), srange(node.binary_operator_loc)],
516
584
  visit(node.value)
517
585
  )
518
586
  end
519
587
 
520
588
  # Foo::Bar &&= baz
521
589
  # ^^^^^^^^^^^^^^^^
522
- alias visit_constant_path_and_write_node visit_constant_path_operator_write_node
590
+ def visit_constant_path_and_write_node(node)
591
+ builder.op_assign(
592
+ builder.assignable(visit(node.target)),
593
+ [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
594
+ visit(node.value)
595
+ )
596
+ end
523
597
 
524
598
  # Foo::Bar ||= baz
525
599
  # ^^^^^^^^^^^^^^^^
526
- alias visit_constant_path_or_write_node visit_constant_path_operator_write_node
600
+ def visit_constant_path_or_write_node(node)
601
+ builder.op_assign(
602
+ builder.assignable(visit(node.target)),
603
+ [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
604
+ visit(node.value)
605
+ )
606
+ end
527
607
 
528
608
  # Foo::Bar, = baz
529
609
  # ^^^^^^^^
@@ -711,18 +791,30 @@ module Prism
711
791
  def visit_global_variable_operator_write_node(node)
712
792
  builder.op_assign(
713
793
  builder.assignable(builder.gvar(token(node.name_loc))),
714
- [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
794
+ [node.binary_operator_loc.slice.chomp("="), srange(node.binary_operator_loc)],
715
795
  visit(node.value)
716
796
  )
717
797
  end
718
798
 
719
799
  # $foo &&= bar
720
800
  # ^^^^^^^^^^^^
721
- alias visit_global_variable_and_write_node visit_global_variable_operator_write_node
801
+ def visit_global_variable_and_write_node(node)
802
+ builder.op_assign(
803
+ builder.assignable(builder.gvar(token(node.name_loc))),
804
+ [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
805
+ visit(node.value)
806
+ )
807
+ end
722
808
 
723
809
  # $foo ||= bar
724
810
  # ^^^^^^^^^^^^
725
- alias visit_global_variable_or_write_node visit_global_variable_operator_write_node
811
+ def visit_global_variable_or_write_node(node)
812
+ builder.op_assign(
813
+ builder.assignable(builder.gvar(token(node.name_loc))),
814
+ [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
815
+ visit(node.value)
816
+ )
817
+ end
726
818
 
727
819
  # $foo, = bar
728
820
  # ^^^^
@@ -803,7 +895,7 @@ module Prism
803
895
  # 1i
804
896
  # ^^
805
897
  def visit_imaginary_node(node)
806
- visit_numeric(node, builder.complex([imaginary_value(node), srange(node.location)]))
898
+ visit_numeric(node, builder.complex([Complex(0, node.numeric.value), srange(node.location)]))
807
899
  end
808
900
 
809
901
  # { foo: }
@@ -857,18 +949,46 @@ module Prism
857
949
  visit_all(arguments),
858
950
  token(node.closing_loc)
859
951
  ),
860
- [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
952
+ [node.binary_operator_loc.slice.chomp("="), srange(node.binary_operator_loc)],
861
953
  visit(node.value)
862
954
  )
863
955
  end
864
956
 
865
957
  # foo[bar] &&= baz
866
958
  # ^^^^^^^^^^^^^^^^
867
- alias visit_index_and_write_node visit_index_operator_write_node
959
+ def visit_index_and_write_node(node)
960
+ arguments = node.arguments&.arguments || []
961
+ arguments << node.block if node.block
962
+
963
+ builder.op_assign(
964
+ builder.index(
965
+ visit(node.receiver),
966
+ token(node.opening_loc),
967
+ visit_all(arguments),
968
+ token(node.closing_loc)
969
+ ),
970
+ [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
971
+ visit(node.value)
972
+ )
973
+ end
868
974
 
869
975
  # foo[bar] ||= baz
870
976
  # ^^^^^^^^^^^^^^^^
871
- alias visit_index_or_write_node visit_index_operator_write_node
977
+ def visit_index_or_write_node(node)
978
+ arguments = node.arguments&.arguments || []
979
+ arguments << node.block if node.block
980
+
981
+ builder.op_assign(
982
+ builder.index(
983
+ visit(node.receiver),
984
+ token(node.opening_loc),
985
+ visit_all(arguments),
986
+ token(node.closing_loc)
987
+ ),
988
+ [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
989
+ visit(node.value)
990
+ )
991
+ end
872
992
 
873
993
  # foo[bar], = 1
874
994
  # ^^^^^^^^
@@ -902,18 +1022,30 @@ module Prism
902
1022
  def visit_instance_variable_operator_write_node(node)
903
1023
  builder.op_assign(
904
1024
  builder.assignable(builder.ivar(token(node.name_loc))),
905
- [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
1025
+ [node.binary_operator_loc.slice.chomp("="), srange(node.binary_operator_loc)],
906
1026
  visit(node.value)
907
1027
  )
908
1028
  end
909
1029
 
910
1030
  # @foo &&= bar
911
1031
  # ^^^^^^^^^^^^
912
- alias visit_instance_variable_and_write_node visit_instance_variable_operator_write_node
1032
+ def visit_instance_variable_and_write_node(node)
1033
+ builder.op_assign(
1034
+ builder.assignable(builder.ivar(token(node.name_loc))),
1035
+ [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
1036
+ visit(node.value)
1037
+ )
1038
+ end
913
1039
 
914
1040
  # @foo ||= bar
915
1041
  # ^^^^^^^^^^^^
916
- alias visit_instance_variable_or_write_node visit_instance_variable_operator_write_node
1042
+ def visit_instance_variable_or_write_node(node)
1043
+ builder.op_assign(
1044
+ builder.assignable(builder.ivar(token(node.name_loc))),
1045
+ [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
1046
+ visit(node.value)
1047
+ )
1048
+ end
917
1049
 
918
1050
  # @foo, = bar
919
1051
  # ^^^^
@@ -946,36 +1078,7 @@ module Prism
946
1078
  # ^^^^^^^^^^^^
947
1079
  def visit_interpolated_string_node(node)
948
1080
  if node.heredoc?
949
- children, closing = visit_heredoc(node)
950
- opening = token(node.opening_loc)
951
-
952
- start_offset = node.opening_loc.end_offset + 1
953
- end_offset = node.parts.first.location.start_offset
954
-
955
- # In the below case, the offsets should be the same:
956
- #
957
- # <<~HEREDOC
958
- # a #{b}
959
- # HEREDOC
960
- #
961
- # But in this case, the end_offset would be greater than the start_offset:
962
- #
963
- # <<~HEREDOC
964
- # #{b}
965
- # HEREDOC
966
- #
967
- # So we need to make sure the result node's heredoc range is correct, without updating the children
968
- result = if start_offset < end_offset
969
- # We need to add a padding string to ensure that the heredoc has correct range for its body
970
- padding_string_node = builder.string_internal(["", srange_offsets(start_offset, end_offset)])
971
- node_with_correct_location = builder.string_compose(opening, [padding_string_node, *children], closing)
972
- # But the padding string should not be included in the final AST, so we need to update the result's children
973
- node_with_correct_location.updated(:dstr, children)
974
- else
975
- builder.string_compose(opening, children, closing)
976
- end
977
-
978
- return result
1081
+ return visit_heredoc(node) { |children, closing| builder.string_compose(token(node.opening_loc), children, closing) }
979
1082
  end
980
1083
 
981
1084
  parts = if node.parts.one? { |part| part.type == :string_node }
@@ -1019,8 +1122,7 @@ module Prism
1019
1122
  # ^^^^^^^^^^^^
1020
1123
  def visit_interpolated_x_string_node(node)
1021
1124
  if node.heredoc?
1022
- children, closing = visit_heredoc(node)
1023
- builder.xstring_compose(token(node.opening_loc), children, closing)
1125
+ visit_heredoc(node) { |children, closing| builder.xstring_compose(token(node.opening_loc), children, closing) }
1024
1126
  else
1025
1127
  builder.xstring_compose(
1026
1128
  token(node.opening_loc),
@@ -1030,6 +1132,12 @@ module Prism
1030
1132
  end
1031
1133
  end
1032
1134
 
1135
+ # -> { it }
1136
+ # ^^
1137
+ def visit_it_local_variable_read_node(node)
1138
+ builder.ident([:it, srange(node.location)]).updated(:lvar)
1139
+ end
1140
+
1033
1141
  # -> { it }
1034
1142
  # ^^^^^^^^^
1035
1143
  def visit_it_parameters_node(node)
@@ -1083,14 +1191,7 @@ module Prism
1083
1191
  # foo
1084
1192
  # ^^^
1085
1193
  def visit_local_variable_read_node(node)
1086
- name = node.name
1087
-
1088
- # This is just a guess. parser doesn't have support for the implicit
1089
- # `it` variable yet, so we'll probably have to visit this once it
1090
- # does.
1091
- name = :it if name == :"0it"
1092
-
1093
- builder.ident([name, srange(node.location)]).updated(:lvar)
1194
+ builder.ident([node.name, srange(node.location)]).updated(:lvar)
1094
1195
  end
1095
1196
 
1096
1197
  # foo = 1
@@ -1108,18 +1209,30 @@ module Prism
1108
1209
  def visit_local_variable_operator_write_node(node)
1109
1210
  builder.op_assign(
1110
1211
  builder.assignable(builder.ident(token(node.name_loc))),
1111
- [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
1212
+ [node.binary_operator_loc.slice.chomp("="), srange(node.binary_operator_loc)],
1112
1213
  visit(node.value)
1113
1214
  )
1114
1215
  end
1115
1216
 
1116
1217
  # foo &&= bar
1117
1218
  # ^^^^^^^^^^^
1118
- alias visit_local_variable_and_write_node visit_local_variable_operator_write_node
1219
+ def visit_local_variable_and_write_node(node)
1220
+ builder.op_assign(
1221
+ builder.assignable(builder.ident(token(node.name_loc))),
1222
+ [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
1223
+ visit(node.value)
1224
+ )
1225
+ end
1119
1226
 
1120
1227
  # foo ||= bar
1121
1228
  # ^^^^^^^^^^^
1122
- alias visit_local_variable_or_write_node visit_local_variable_operator_write_node
1229
+ def visit_local_variable_or_write_node(node)
1230
+ builder.op_assign(
1231
+ builder.assignable(builder.ident(token(node.name_loc))),
1232
+ [node.operator_loc.slice.chomp("="), srange(node.operator_loc)],
1233
+ visit(node.value)
1234
+ )
1235
+ end
1123
1236
 
1124
1237
  # foo, = bar
1125
1238
  # ^^^
@@ -1182,13 +1295,9 @@ module Prism
1182
1295
  # foo, bar = baz
1183
1296
  # ^^^^^^^^
1184
1297
  def visit_multi_target_node(node)
1185
- elements = [*node.lefts]
1186
- elements << node.rest if !node.rest.nil? && !node.rest.is_a?(ImplicitRestNode)
1187
- elements.concat(node.rights)
1188
-
1189
1298
  builder.multi_lhs(
1190
1299
  token(node.lparen_loc),
1191
- visit_all(elements),
1300
+ visit_all(multi_target_elements(node)),
1192
1301
  token(node.rparen_loc)
1193
1302
  )
1194
1303
  end
@@ -1196,9 +1305,11 @@ module Prism
1196
1305
  # foo, bar = baz
1197
1306
  # ^^^^^^^^^^^^^^
1198
1307
  def visit_multi_write_node(node)
1199
- elements = [*node.lefts]
1200
- elements << node.rest if !node.rest.nil? && !node.rest.is_a?(ImplicitRestNode)
1201
- elements.concat(node.rights)
1308
+ elements = multi_target_elements(node)
1309
+
1310
+ if elements.length == 1 && elements.first.is_a?(MultiTargetNode)
1311
+ elements = multi_target_elements(elements.first)
1312
+ end
1202
1313
 
1203
1314
  builder.multi_assign(
1204
1315
  builder.multi_lhs(
@@ -1279,12 +1390,12 @@ module Prism
1279
1390
 
1280
1391
  if node.requireds.any?
1281
1392
  node.requireds.each do |required|
1282
- if required.is_a?(RequiredParameterNode)
1283
- params << visit(required)
1284
- else
1285
- compiler = copy_compiler(in_destructure: true)
1286
- params << required.accept(compiler)
1287
- end
1393
+ params <<
1394
+ if required.is_a?(RequiredParameterNode)
1395
+ visit(required)
1396
+ else
1397
+ required.accept(copy_compiler(in_destructure: true))
1398
+ end
1288
1399
  end
1289
1400
  end
1290
1401
 
@@ -1293,12 +1404,12 @@ module Prism
1293
1404
 
1294
1405
  if node.posts.any?
1295
1406
  node.posts.each do |post|
1296
- if post.is_a?(RequiredParameterNode)
1297
- params << visit(post)
1298
- else
1299
- compiler = copy_compiler(in_destructure: true)
1300
- params << post.accept(compiler)
1301
- end
1407
+ params <<
1408
+ if post.is_a?(RequiredParameterNode)
1409
+ visit(post)
1410
+ else
1411
+ post.accept(copy_compiler(in_destructure: true))
1412
+ end
1302
1413
  end
1303
1414
  end
1304
1415
 
@@ -1384,7 +1495,7 @@ module Prism
1384
1495
  # 1r
1385
1496
  # ^^
1386
1497
  def visit_rational_node(node)
1387
- visit_numeric(node, builder.rational([rational_value(node), srange(node.location)]))
1498
+ visit_numeric(node, builder.rational([node.value, srange(node.location)]))
1388
1499
  end
1389
1500
 
1390
1501
  # redo
@@ -1396,9 +1507,20 @@ module Prism
1396
1507
  # /foo/
1397
1508
  # ^^^^^
1398
1509
  def visit_regular_expression_node(node)
1510
+ content = node.content
1511
+ parts =
1512
+ if content.include?("\n")
1513
+ offset = node.content_loc.start_offset
1514
+ content.lines.map do |line|
1515
+ builder.string_internal([line, srange_offsets(offset, offset += line.bytesize)])
1516
+ end
1517
+ else
1518
+ [builder.string_internal(token(node.content_loc))]
1519
+ end
1520
+
1399
1521
  builder.regexp_compose(
1400
1522
  token(node.opening_loc),
1401
- [builder.string_internal(token(node.content_loc))],
1523
+ parts,
1402
1524
  [node.closing[0], srange_offsets(node.closing_loc.start_offset, node.closing_loc.start_offset + 1)],
1403
1525
  builder.regexp_options([node.closing[1..], srange_offsets(node.closing_loc.start_offset + 1, node.closing_loc.end_offset)])
1404
1526
  )
@@ -1544,10 +1666,11 @@ module Prism
1544
1666
  # ^^^^^
1545
1667
  def visit_string_node(node)
1546
1668
  if node.heredoc?
1547
- children, closing = visit_heredoc(node.to_interpolated)
1548
- builder.string_compose(token(node.opening_loc), children, closing)
1669
+ visit_heredoc(node.to_interpolated) { |children, closing| builder.string_compose(token(node.opening_loc), children, closing) }
1549
1670
  elsif node.opening == "?"
1550
1671
  builder.character([node.unescaped, srange(node.location)])
1672
+ elsif node.opening&.start_with?("%") && node.unescaped.empty?
1673
+ builder.string_compose(token(node.opening_loc), [], token(node.closing_loc))
1551
1674
  else
1552
1675
  content_lines = node.content.lines
1553
1676
  unescaped_lines = node.unescaped.lines
@@ -1747,8 +1870,7 @@ module Prism
1747
1870
  # ^^^^^
1748
1871
  def visit_x_string_node(node)
1749
1872
  if node.heredoc?
1750
- children, closing = visit_heredoc(node.to_interpolated)
1751
- builder.xstring_compose(token(node.opening_loc), children, closing)
1873
+ visit_heredoc(node.to_interpolated) { |children, closing| builder.xstring_compose(token(node.opening_loc), children, closing) }
1752
1874
  else
1753
1875
  parts = if node.unescaped.lines.one?
1754
1876
  [builder.string_internal([node.unescaped, srange(node.content_loc)])]
@@ -1810,10 +1932,12 @@ module Prism
1810
1932
  forwarding
1811
1933
  end
1812
1934
 
1813
- # Because we have mutated the AST to allow for newlines in the middle of
1814
- # a rational, we need to manually handle the value here.
1815
- def imaginary_value(node)
1816
- Complex(0, node.numeric.is_a?(RationalNode) ? rational_value(node.numeric) : node.numeric.value)
1935
+ # Returns the set of targets for a MultiTargetNode or a MultiWriteNode.
1936
+ def multi_target_elements(node)
1937
+ elements = [*node.lefts]
1938
+ elements << node.rest if !node.rest.nil? && !node.rest.is_a?(ImplicitRestNode)
1939
+ elements.concat(node.rights)
1940
+ elements
1817
1941
  end
1818
1942
 
1819
1943
  # Negate the value of a numeric node. This is a special case where you
@@ -1825,7 +1949,9 @@ module Prism
1825
1949
  case receiver.type
1826
1950
  when :integer_node, :float_node
1827
1951
  receiver.copy(value: -receiver.value, location: message_loc.join(receiver.location))
1828
- when :rational_node, :imaginary_node
1952
+ when :rational_node
1953
+ receiver.copy(numerator: -receiver.numerator, location: message_loc.join(receiver.location))
1954
+ when :imaginary_node
1829
1955
  receiver.copy(numeric: numeric_negate(message_loc, receiver.numeric), location: message_loc.join(receiver.location))
1830
1956
  end
1831
1957
  end
@@ -1844,16 +1970,6 @@ module Prism
1844
1970
  parameters.block.nil?
1845
1971
  end
1846
1972
 
1847
- # Because we have mutated the AST to allow for newlines in the middle of
1848
- # a rational, we need to manually handle the value here.
1849
- def rational_value(node)
1850
- if node.numeric.is_a?(IntegerNode)
1851
- Rational(node.numeric.value)
1852
- else
1853
- Rational(node.slice.gsub(/\s/, "").chomp("r"))
1854
- end
1855
- end
1856
-
1857
1973
  # Locations in the parser gem AST are generated using this class. We
1858
1974
  # store a reference to its constant to make it slightly faster to look
1859
1975
  # up.
@@ -1876,7 +1992,7 @@ module Prism
1876
1992
  # Note that end_offset is allowed to be nil, in which case this will
1877
1993
  # search until the end of the string.
1878
1994
  def srange_find(start_offset, end_offset, tokens)
1879
- if (match = source_buffer.source.byteslice(start_offset...end_offset).match(/(\s*)(#{tokens.join("|")})/))
1995
+ if (match = source_buffer.source.byteslice(start_offset...end_offset).match(/\A(\s*)(#{tokens.join("|")})/))
1880
1996
  _, whitespace, token = *match
1881
1997
  token_offset = start_offset + whitespace.bytesize
1882
1998
 
@@ -1907,7 +2023,8 @@ module Prism
1907
2023
  token(parameters.opening_loc),
1908
2024
  if procarg0?(parameters.parameters)
1909
2025
  parameter = parameters.parameters.requireds.first
1910
- [builder.procarg0(visit(parameter))].concat(visit_all(parameters.locals))
2026
+ visited = parameter.is_a?(RequiredParameterNode) ? visit(parameter) : parameter.accept(copy_compiler(in_destructure: true))
2027
+ [builder.procarg0(visited)].concat(visit_all(parameters.locals))
1911
2028
  else
1912
2029
  visit(parameters)
1913
2030
  end,
@@ -1923,29 +2040,55 @@ module Prism
1923
2040
  end
1924
2041
  end
1925
2042
 
2043
+ # The parser gem automatically converts \r\n to \n, meaning our offsets
2044
+ # need to be adjusted to always subtract 1 from the length.
2045
+ def chomped_bytesize(line)
2046
+ chomped = line.chomp
2047
+ chomped.bytesize + (chomped == line ? 0 : 1)
2048
+ end
2049
+
1926
2050
  # Visit a heredoc that can be either a string or an xstring.
1927
2051
  def visit_heredoc(node)
1928
2052
  children = Array.new
2053
+ indented = false
2054
+
2055
+ # If this is a dedenting heredoc, then we need to insert the opening
2056
+ # content into the children as well.
2057
+ if node.opening.start_with?("<<~") && node.parts.length > 0 && !node.parts.first.is_a?(StringNode)
2058
+ location = node.parts.first.location
2059
+ location = location.copy(start_offset: location.start_offset - location.start_line_slice.bytesize)
2060
+ children << builder.string_internal(token(location))
2061
+ indented = true
2062
+ end
2063
+
1929
2064
  node.parts.each do |part|
1930
2065
  pushing =
1931
2066
  if part.is_a?(StringNode) && part.unescaped.include?("\n")
1932
- unescaped = part.unescaped.lines(chomp: true)
1933
- escaped = part.content.lines(chomp: true)
2067
+ unescaped = part.unescaped.lines
2068
+ escaped = part.content.lines
1934
2069
 
1935
- escaped_lengths =
1936
- if node.opening.end_with?("'")
1937
- escaped.map { |line| line.bytesize + 1 }
1938
- else
1939
- escaped.chunk_while { |before, after| before.match?(/(?<!\\)\\$/) }.map { |line| line.join.bytesize + line.length }
2070
+ escaped_lengths = []
2071
+ normalized_lengths = []
2072
+
2073
+ if node.opening.end_with?("'")
2074
+ escaped.each do |line|
2075
+ escaped_lengths << line.bytesize
2076
+ normalized_lengths << chomped_bytesize(line)
1940
2077
  end
2078
+ else
2079
+ escaped
2080
+ .chunk_while { |before, after| before.match?(/(?<!\\)\\\r?\n$/) }
2081
+ .each do |lines|
2082
+ escaped_lengths << lines.sum(&:bytesize)
2083
+ normalized_lengths << lines.sum { |line| chomped_bytesize(line) }
2084
+ end
2085
+ end
1941
2086
 
1942
2087
  start_offset = part.location.start_offset
1943
- end_offset = nil
1944
2088
 
1945
- unescaped.zip(escaped_lengths).map do |unescaped_line, escaped_length|
1946
- end_offset = start_offset + (escaped_length || 0)
1947
- inner_part = builder.string_internal(["#{unescaped_line}\n", srange_offsets(start_offset, end_offset)])
1948
- start_offset = end_offset
2089
+ unescaped.map.with_index do |unescaped_line, index|
2090
+ inner_part = builder.string_internal([unescaped_line, srange_offsets(start_offset, start_offset + normalized_lengths.fetch(index, 0))])
2091
+ start_offset += escaped_lengths.fetch(index, 0)
1949
2092
  inner_part
1950
2093
  end
1951
2094
  else
@@ -1956,7 +2099,12 @@ module Prism
1956
2099
  if child.type == :str && child.children.last == ""
1957
2100
  # nothing
1958
2101
  elsif child.type == :str && children.last && children.last.type == :str && !children.last.children.first.end_with?("\n")
1959
- children.last.children.first << child.children.first
2102
+ appendee = children[-1]
2103
+
2104
+ location = appendee.loc
2105
+ location = location.with_expression(location.expression.join(child.loc.expression))
2106
+
2107
+ children[-1] = appendee.updated(:str, [appendee.children.first << child.children.first], location: location)
1960
2108
  else
1961
2109
  children << child
1962
2110
  end
@@ -1965,8 +2113,10 @@ module Prism
1965
2113
 
1966
2114
  closing = node.closing
1967
2115
  closing_t = [closing.chomp, srange_offsets(node.closing_loc.start_offset, node.closing_loc.end_offset - (closing[/\s+$/]&.length || 0))]
2116
+ composed = yield children, closing_t
1968
2117
 
1969
- [children, closing_t]
2118
+ composed = composed.updated(nil, children[1..-1]) if indented
2119
+ composed
1970
2120
  end
1971
2121
 
1972
2122
  # Visit a numeric node and account for the optional sign.