Repository: ruby/prism
Pull request #1880 (2023-11-19 11:28:17)

feat: Adds macCroatian encoding
- Based on:
  https://en.wikipedia.org/wiki/Mac_OS_Croatian_encoding
  https://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CROATIAN.TXT


Questions:

- ~Would it make sense to add what the values mean in the bitfields for `src/enc/pm_tables.c`? Would make it easier for contributions atleast.~
- ~Should greek letters be included as well? Based on the unicode document it said `letter` so we considered it alphanumeric.~
- ~We were thrown off by some of the added values for `mac*` encodings because a lot of them have `0` for the codepoints 128-255 - which seems wrong for most of them. Should they be updated?~



Patch:
diff --git a/bin/encodings b/bin/encodings
index 579c4071..77de0b75 100755
--- a/bin/encodings
+++ b/bin/encodings
@@ -66,6 +66,7 @@ when "ibm860"       then table(Encoding::IBM860)
 when "ibm861"       then table(Encoding::IBM861)
 when "ibm862"       then table(Encoding::IBM862)
 when "ibm864"       then table(Encoding::IBM864)
+when "ibm865"       then table(Encoding::IBM865)
 when "ibm866"       then table(Encoding::IBM866)
 when "iso-8859-1"   then table(Encoding::ISO8859_1)
 when "iso-8859-2"   then table(Encoding::ISO8859_2)
@@ -84,6 +85,7 @@ when "iso-8859-15"  then table(Encoding::ISO8859_15)
 when "iso-8859-16"  then table(Encoding::ISO8859_16)
 when "koi8-r"       then table(Encoding::KOI8_R)
 when "maccenteuro"  then table(Encoding::MACCENTEURO)
+when "maccroatian"  then table(Encoding::MACCROATIAN)
 when "maccyrillic"  then table(Encoding::MACCYRILLIC)
 when "macgreek"     then table(Encoding::MACGREEK)
 when "maciceland"   then table(Encoding::MACICELAND)
diff --git a/docs/encoding.md b/docs/encoding.md
index eb5926ae..4ff8aa9c 100644
--- a/docs/encoding.md
+++ b/docs/encoding.md
@@ -32,6 +32,7 @@ The key of the comment can be either "encoding" or "coding". The value of the co
 * `IBM861`
 * `IBM862`
 * `IBM864`
+* `IBM865`
 * `IBM866`
 * `ISO-8859-1`
 * `ISO-8859-2`
@@ -50,6 +51,7 @@ The key of the comment can be either "encoding" or "coding". The value of the co
 * `ISO-8859-16`
 * `KOI8-R`
 * `macCentEuro`
+* `macCroatian`
 * `macCyrillic`
 * `macGreek`
 * `macIceland`
diff --git a/include/prism/diagnostic.h b/include/prism/diagnostic.h
index 10b547ae..c963289c 100644
--- a/include/prism/diagnostic.h
+++ b/include/prism/diagnostic.h
@@ -40,12 +40,14 @@ typedef enum {
     PM_ERR_ALIAS_ARGUMENT,
     PM_ERR_AMPAMPEQ_MULTI_ASSIGN,
     PM_ERR_ARGUMENT_AFTER_BLOCK,
+    PM_ERR_ARGUMENT_AFTER_FORWARDING_ELLIPSES,
     PM_ERR_ARGUMENT_BARE_HASH,
     PM_ERR_ARGUMENT_BLOCK_MULTI,
     PM_ERR_ARGUMENT_FORMAL_CLASS,
     PM_ERR_ARGUMENT_FORMAL_CONSTANT,
     PM_ERR_ARGUMENT_FORMAL_GLOBAL,
     PM_ERR_ARGUMENT_FORMAL_IVAR,
+    PM_ERR_ARGUMENT_FORWARDING_UNBOUND,
     PM_ERR_ARGUMENT_NO_FORWARDING_AMP,
     PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES,
     PM_ERR_ARGUMENT_NO_FORWARDING_STAR,
diff --git a/include/prism/enc/pm_encoding.h b/include/prism/enc/pm_encoding.h
index bd7d7ba1..7279dfd7 100644
--- a/include/prism/enc/pm_encoding.h
+++ b/include/prism/enc/pm_encoding.h
@@ -177,6 +177,7 @@ extern pm_encoding_t pm_encoding_ibm860;
 extern pm_encoding_t pm_encoding_ibm861;
 extern pm_encoding_t pm_encoding_ibm862;
 extern pm_encoding_t pm_encoding_ibm864;
+extern pm_encoding_t pm_encoding_ibm865;
 extern pm_encoding_t pm_encoding_ibm866;
 extern pm_encoding_t pm_encoding_iso_8859_1;
 extern pm_encoding_t pm_encoding_iso_8859_2;
@@ -195,6 +196,7 @@ extern pm_encoding_t pm_encoding_iso_8859_15;
 extern pm_encoding_t pm_encoding_iso_8859_16;
 extern pm_encoding_t pm_encoding_koi8_r;
 extern pm_encoding_t pm_encoding_mac_cent_euro;
+extern pm_encoding_t pm_encoding_mac_croatian;
 extern pm_encoding_t pm_encoding_mac_cyrillic;
 extern pm_encoding_t pm_encoding_mac_greek;
 extern pm_encoding_t pm_encoding_mac_iceland;
diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb
index e1d3e0dc..c910fd3a 100644
--- a/lib/prism/ffi.rb
+++ b/lib/prism/ffi.rb
@@ -230,7 +230,7 @@ module Prism
         loader = Serialize::Loader.new(source, buffer.read)
 
         loader.load_header
-        loader.load_force_encoding
+        loader.load_encoding
         loader.load_start_line
         loader.load_comments
       end
diff --git a/lib/prism/parse_result.rb b/lib/prism/parse_result.rb
index 170a529b..50c23bce 100644
--- a/lib/prism/parse_result.rb
+++ b/lib/prism/parse_result.rb
@@ -25,40 +25,50 @@ module Prism
 
     # Perform a byteslice on the source code using the given byte offset and
     # byte length.
-    def slice(offset, length)
-      source.byteslice(offset, length)
+    def slice(byte_offset, length)
+      source.byteslice(byte_offset, length)
     end
 
     # Binary search through the offsets to find the line number for the given
     # byte offset.
-    def line(value)
-      start_line + find_line(value)
+    def line(byte_offset)
+      start_line + find_line(byte_offset)
     end
 
     # Return the byte offset of the start of the line corresponding to the given
     # byte offset.
-    def line_offset(value)
-      offsets[find_line(value)]
+    def line_start(byte_offset)
+      offsets[find_line(byte_offset)]
     end
 
     # Return the column number for the given byte offset.
-    def column(value)
-      value - offsets[find_line(value)]
+    def column(byte_offset)
+      byte_offset - line_start(byte_offset)
+    end
+
+    # Return the character offset for the given byte offset.
+    def character_offset(byte_offset)
+      source.byteslice(0, byte_offset).length
+    end
+
+    # Return the column number in characters for the given byte offset.
+    def character_column(byte_offset)
+      character_offset(byte_offset) - character_offset(line_start(byte_offset))
     end
 
     private
 
     # Binary search through the offsets to find the line number for the given
     # byte offset.
-    def find_line(value)
+    def find_line(byte_offset)
       left = 0
       right = offsets.length - 1
 
       while left <= right
         mid = left + (right - left) / 2
-        return mid if offsets[mid] == value
+        return mid if offsets[mid] == byte_offset
 
-        if offsets[mid] < value
+        if offsets[mid] < byte_offset
           left = mid + 1
         else
           right = mid - 1
@@ -121,11 +131,23 @@ module Prism
       source.slice(start_offset, length)
     end
 
+    # The character offset from the beginning of the source where this location
+    # starts.
+    def start_character_offset
+      source.character_offset(start_offset)
+    end
+
     # The byte offset from the beginning of the source where this location ends.
     def end_offset
       start_offset + length
     end
 
+    # The character offset from the beginning of the source where this location
+    # ends.
+    def end_character_offset
+      source.character_offset(end_offset)
+    end
+
     # The line number where this location starts.
     def start_line
       source.line(start_offset)
@@ -133,7 +155,7 @@ module Prism
 
     # The content of the line where this location starts before this location.
     def start_line_slice
-      offset = source.line_offset(start_offset)
+      offset = source.line_start(start_offset)
       source.slice(offset, start_offset - offset)
     end
 
@@ -148,12 +170,24 @@ module Prism
       source.column(start_offset)
     end
 
+    # The column number in characters where this location ends from the start of
+    # the line.
+    def start_character_column
+      source.character_column(start_offset)
+    end
+
     # The column number in bytes where this location ends from the start of the
     # line.
     def end_column
       source.column(end_offset)
     end
 
+    # The column number in characters where this location ends from the start of
+    # the line.
+    def end_character_column
+      source.character_column(end_offset)
+    end
+
     # Implement the hash pattern matching interface for Location.
     def deconstruct_keys(keys)
       { start_offset: start_offset, end_offset: end_offset }
diff --git a/src/diagnostic.c b/src/diagnostic.c
index 2c59e4ef..c1d930a4 100644
--- a/src/diagnostic.c
+++ b/src/diagnostic.c
@@ -54,12 +54,14 @@ static const char* const diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = {
     [PM_ERR_ALIAS_ARGUMENT]                     = "Invalid argument being passed to `alias`; expected a bare word, symbol, constant, or global variable",
     [PM_ERR_AMPAMPEQ_MULTI_ASSIGN]              = "Unexpected `&&=` in a multiple assignment",
     [PM_ERR_ARGUMENT_AFTER_BLOCK]               = "Unexpected argument after a block argument",
+    [PM_ERR_ARGUMENT_AFTER_FORWARDING_ELLIPSES] = "Unexpected argument after `...`",
     [PM_ERR_ARGUMENT_BARE_HASH]                 = "Unexpected bare hash argument",
     [PM_ERR_ARGUMENT_BLOCK_MULTI]               = "Multiple block arguments; only one block is allowed",
     [PM_ERR_ARGUMENT_FORMAL_CLASS]              = "Invalid formal argument; formal argument cannot be a class variable",
     [PM_ERR_ARGUMENT_FORMAL_CONSTANT]           = "Invalid formal argument; formal argument cannot be a constant",
     [PM_ERR_ARGUMENT_FORMAL_GLOBAL]             = "Invalid formal argument; formal argument cannot be a global variable",
     [PM_ERR_ARGUMENT_FORMAL_IVAR]               = "Invalid formal argument; formal argument cannot be an instance variable",
+    [PM_ERR_ARGUMENT_FORWARDING_UNBOUND]        = "Unexpected `...` in an non-parenthesized call",
     [PM_ERR_ARGUMENT_NO_FORWARDING_AMP]         = "Unexpected `&` when the parent method is not forwarding",
     [PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES]    = "Unexpected `...` when the parent method is not forwarding",
     [PM_ERR_ARGUMENT_NO_FORWARDING_STAR]        = "Unexpected `*` when the parent method is not forwarding",
diff --git a/src/enc/pm_tables.c b/src/enc/pm_tables.c
index a4d47999..d0414590 100644
--- a/src/enc/pm_tables.c
+++ b/src/enc/pm_tables.c
@@ -384,6 +384,30 @@ static uint8_t pm_encoding_ibm864_table[256] = {
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Fx
 };
 
+/**
+ * Each element of the following table contains a bitfield that indicates a
+ * piece of information about the corresponding IBM865 character.
+ */
+static uint8_t pm_encoding_ibm865_table[256] = {
+//  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2x
+    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, // 3x
+    0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // 4x
+    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, // 5x
+    0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 6x
+    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, // 7x
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8x
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9x
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Ax
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Bx
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Cx
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Dx
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Ex
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Fx
+};
+
 /**
  * Each element of the following table contains a bitfield that indicates a
  * piece of information about the corresponding IBM866 character.
@@ -817,6 +841,30 @@ static uint8_t pm_encoding_mac_cent_euro_table[256] = {
 };
 
 /**
+ * Each element of the following table contains a bitfield that indicates a
+ * piece of information about the corresponding macCroatian character.
+ */
+static uint8_t pm_encoding_mac_croatian_table[256] = {
+//  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2x
+    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, // 3x
+    0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // 4x
+    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, // 5x
+    0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 6x
+    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, // 7x
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8x
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9x
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Ax
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Bx
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Cx
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Dx
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Ex
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Fx
+};
+
+ /**
  * Each element of the following table contains a bitfield that indicates a
  * piece of information about the corresponding macCyrillic character.
  */
@@ -1328,6 +1376,7 @@ PRISM_ENCODING_TABLE(ibm860)
 PRISM_ENCODING_TABLE(ibm861)
 PRISM_ENCODING_TABLE(ibm862)
 PRISM_ENCODING_TABLE(ibm864)
+PRISM_ENCODING_TABLE(ibm865)
 PRISM_ENCODING_TABLE(ibm866)
 PRISM_ENCODING_TABLE(iso_8859_1)
 PRISM_ENCODING_TABLE(iso_8859_2)
@@ -1346,6 +1395,7 @@ PRISM_ENCODING_TABLE(iso_8859_15)
 PRISM_ENCODING_TABLE(iso_8859_16)
 PRISM_ENCODING_TABLE(koi8_r)
 PRISM_ENCODING_TABLE(mac_cent_euro)
+PRISM_ENCODING_TABLE(mac_croatian)
 PRISM_ENCODING_TABLE(mac_cyrillic)
 PRISM_ENCODING_TABLE(mac_greek)
 PRISM_ENCODING_TABLE(mac_iceland)
@@ -1537,6 +1587,16 @@ pm_encoding_t pm_encoding_ibm864 = {
     .multibyte = false
 };
 
+/** IBM866 */
+pm_encoding_t pm_encoding_ibm865 = {
+    .name = "IBM865",
+    .char_width = pm_encoding_single_char_width,
+    .alnum_char = pm_encoding_ibm865_alnum_char,
+    .alpha_char = pm_encoding_ibm865_alpha_char,
+    .isupper_char = pm_encoding_ibm865_isupper_char,
+    .multibyte = false
+};
+
 /** IBM866 */
 pm_encoding_t pm_encoding_ibm866 = {
     .name = "IBM866",
@@ -1717,6 +1777,16 @@ pm_encoding_t pm_encoding_mac_cent_euro = {
     .multibyte = false
 };
 
+/** macCroatian */
+pm_encoding_t pm_encoding_mac_croatian = {
+    .name = "macCroatian",
+    .char_width = pm_encoding_single_char_width,
+    .alnum_char = pm_encoding_mac_croatian_alnum_char,
+    .alpha_char = pm_encoding_mac_croatian_alpha_char,
+    .isupper_char = pm_encoding_mac_croatian_isupper_char,
+    .multibyte = false
+};
+
 /** macCyrillic */
 pm_encoding_t pm_encoding_mac_cyrillic = {
     .name = "macCyrillic",
diff --git a/src/prism.c b/src/prism.c
index 850b35c3..a0751efd 100644
--- a/src/prism.c
+++ b/src/prism.c
@@ -6077,6 +6077,7 @@ parser_lex_magic_comment_encoding_value(pm_parser_t *parser, const uint8_t *star
                 ENCODING1("CP861", pm_encoding_ibm861);
                 ENCODING1("CP862", pm_encoding_ibm862);
                 ENCODING1("CP864", pm_encoding_ibm864);
+                ENCODING1("CP865", pm_encoding_ibm865);
                 ENCODING1("CP866", pm_encoding_ibm866);
                 ENCODING1("CP874", pm_encoding_windows_874);
                 ENCODING1("CP878", pm_encoding_koi8_r);
@@ -6118,6 +6119,7 @@ parser_lex_magic_comment_encoding_value(pm_parser_t *parser, const uint8_t *star
                 ENCODING1("IBM861", pm_encoding_ibm861);
                 ENCODING1("IBM862", pm_encoding_ibm862);
                 ENCODING1("IBM864", pm_encoding_ibm864);
+                ENCODING1("IBM865", pm_encoding_ibm865);
                 ENCODING1("IBM866", pm_encoding_ibm866);
                 ENCODING2("ISO-8859-1", "ISO8859-1", pm_encoding_iso_8859_1);
                 ENCODING2("ISO-8859-2", "ISO8859-2", pm_encoding_iso_8859_2);
@@ -6143,6 +6145,7 @@ parser_lex_magic_comment_encoding_value(pm_parser_t *parser, const uint8_t *star
                 break;
             case 'M': case 'm':
                 ENCODING1("macCentEuro", pm_encoding_mac_cent_euro);
+                ENCODING1("macCroatian", pm_encoding_mac_croatian);
                 ENCODING1("macCyrillic", pm_encoding_mac_cyrillic);
                 ENCODING1("macGreek", pm_encoding_mac_greek);
                 ENCODING1("macIceland", pm_encoding_mac_iceland);
@@ -10021,7 +10024,7 @@ typedef enum {
     PM_BINDING_POWER_MODIFIER_RESCUE =  6, // rescue
     PM_BINDING_POWER_COMPOSITION =      8, // and or
     PM_BINDING_POWER_NOT =             10, // not
-    PM_BINDING_POWER_MATCH =           12, // =>
+    PM_BINDING_POWER_MATCH =           12, // => in
     PM_BINDING_POWER_DEFINED =         14, // defined?
     PM_BINDING_POWER_ASSIGNMENT =      16, // = += -= *= /= %= &= |= ^= &&= ||= <<= >>= **=
     PM_BINDING_POWER_TERNARY =         18, // ?:
@@ -10080,12 +10083,7 @@ pm_binding_powers_t pm_binding_powers[PM_TOKEN_MAXIMUM] = {
     [PM_TOKEN_KEYWORD_WHILE_MODIFIER] = LEFT_ASSOCIATIVE(PM_BINDING_POWER_MODIFIER),
 
     // rescue
-    [PM_TOKEN_KEYWORD_RESCUE_MODIFIER] = {
-        PM_BINDING_POWER_ASSIGNMENT,
-        PM_BINDING_POWER_MODIFIER_RESCUE + 1,
-        true,
-        false
-    },
+    [PM_TOKEN_KEYWORD_RESCUE_MODIFIER] = LEFT_ASSOCIATIVE(PM_BINDING_POWER_MODIFIER_RESCUE),
 
     // and or
     [PM_TOKEN_KEYWORD_AND] = LEFT_ASSOCIATIVE(PM_BINDING_POWER_COMPOSITION),
@@ -11037,13 +11035,18 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for
         return;
     }
 
+    bool parsed_first_argument = false;
     bool parsed_bare_hash = false;
     bool parsed_block_argument = false;
+    bool parsed_forwarding_arguments = false;
 
     while (!match1(parser, PM_TOKEN_EOF)) {
         if (parsed_block_argument) {
             pm_parser_err_current(parser, PM_ERR_ARGUMENT_AFTER_BLOCK);
         }
+        if (parsed_forwarding_arguments) {
+            pm_parser_err_current(parser, PM_ERR_ARGUMENT_AFTER_FORWARDING_ELLIPSES);
+        }
 
         pm_node_t *argument = NULL;
 
@@ -11127,9 +11130,13 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for
                         if (pm_parser_local_depth(parser, &parser->previous) == -1) {
                             pm_parser_err_previous(parser, PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES);
                         }
+                        if (parsed_first_argument && terminator == PM_TOKEN_EOF) {
+                            pm_parser_err_previous(parser, PM_ERR_ARGUMENT_FORWARDING_UNBOUND);
+                        }
 
                         argument = (pm_node_t *) pm_forwarding_arguments_node_create(parser, &parser->previous);
                         parse_arguments_append(parser, arguments, argument);
+                        parsed_forwarding_arguments = true;
                         break;
                     }
                 }
@@ -11181,6 +11188,8 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for
             }
         }
 
+        parsed_first_argument = true;
+
         // If parsing the argument failed, we need to stop parsing arguments.
         if (PM_NODE_TYPE_P(argument, PM_MISSING_NODE) || parser->recovering) break;
 
@@ -15644,10 +15653,29 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power) {
 }
 
 static inline pm_node_t *
-parse_assignment_value(pm_parser_t *parser, pm_binding_power_t previous_binding_power, pm_binding_power_t binding_power, pm_diagnostic_id_t diag_id) {
+parse_assignment_value(pm_parser_t *parser, pm_binding_power_t binding_power, pm_diagnostic_id_t diag_id) {
+    pm_node_t *value = parse_value_expression(parser, binding_power, diag_id);
+
+    // Contradicting binding powers, the right-hand-side value of rthe assignment allows the `rescue` modifier.
+    if (match1(parser, PM_TOKEN_KEYWORD_RESCUE_MODIFIER)) {
+        pm_token_t rescue = parser->current;
+        parser_lex(parser);
+        pm_node_t *right = parse_expression(parser, binding_power, PM_ERR_RESCUE_MODIFIER_VALUE);
+
+        return (pm_node_t *) pm_rescue_modifier_node_create(parser, value, &rescue, right);
+    }
+
+    return value;
+}
+
+
+static inline pm_node_t *
+parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding_power, pm_binding_power_t binding_power, pm_diagnostic_id_t diag_id) {
     pm_node_t *value = parse_starred_expression(parser, binding_power, diag_id);
 
+    bool is_single_value = true;
     if (previous_binding_power == PM_BINDING_POWER_STATEMENT && (PM_NODE_TYPE_P(value, PM_SPLAT_NODE) || match1(parser, PM_TOKEN_COMMA))) {
+        is_single_value = false;
         pm_token_t opening = not_provided(parser);
         pm_array_node_t *array = pm_array_node_create(parser, &opening);
 
@@ -15661,6 +15689,15 @@ parse_assignment_value(pm_parser_t *parser, pm_binding_power_t previous_binding_
         }
     }
 
+    // Contradicting binding powers, the right-hand-side value of the assignment allows the `rescue` modifier.
+    if (is_single_value && match1(parser, PM_TOKEN_KEYWORD_RESCUE_MODIFIER)) {
+        pm_token_t rescue = parser->current;
+        parser_lex(parser);
+        pm_node_t *right = parse_expression(parser, binding_power, PM_ERR_RESCUE_MODIFIER_VALUE);
+
+        return (pm_node_t *) pm_rescue_modifier_node_create(parser, value, &rescue, right);
+    }
+
     return value;
 }
 
@@ -15800,7 +15837,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                 /* fallthrough */
                 case PM_CASE_WRITABLE: {
                     parser_lex(parser);
-                    pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL);
+                    pm_node_t *value = parse_assignment_values(parser, previous_binding_power, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL);
                     return parse_write(parser, node, &token, value);
                 }
                 case PM_SPLAT_NODE: {
@@ -15808,7 +15845,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                     pm_multi_target_node_targets_append(parser, multi_target, node);
 
                     parser_lex(parser);
-                    pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL);
+                    pm_node_t *value = parse_assignment_values(parser, previous_binding_power, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL);
                     return parse_write(parser, (pm_node_t *) multi_target, &token, value);
                 }
                 default:
@@ -15830,7 +15867,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                 case PM_GLOBAL_VARIABLE_READ_NODE: {
                     parser_lex(parser);
 
-                    pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
+                    pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
                     pm_node_t *result = (pm_node_t *) pm_global_variable_and_write_node_create(parser, node, &token, value);
 
                     pm_node_destroy(parser, node);
@@ -15839,7 +15876,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                 case PM_CLASS_VARIABLE_READ_NODE: {
                     parser_lex(parser);
 
-                    pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
+                    pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
                     pm_node_t *result = (pm_node_t *) pm_class_variable_and_write_node_create(parser, (pm_class_variable_read_node_t *) node, &token, value);
 
                     pm_node_destroy(parser, node);
@@ -15848,13 +15885,13 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                 case PM_CONSTANT_PATH_NODE: {
                     parser_lex(parser);
 
-                    pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
+                    pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
                     return (pm_node_t *) pm_constant_path_and_write_node_create(parser, (pm_constant_path_node_t *) node, &token, value);
                 }
                 case PM_CONSTANT_READ_NODE: {
                     parser_lex(parser);
 
-                    pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
+                    pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
                     pm_node_t *result = (pm_node_t *) pm_constant_and_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value);
 
                     pm_node_destroy(parser, node);
@@ -15863,7 +15900,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                 case PM_INSTANCE_VARIABLE_READ_NODE: {
                     parser_lex(parser);
 
-                    pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
+                    pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
                     pm_node_t *result = (pm_node_t *) pm_instance_variable_and_write_node_create(parser, (pm_instance_variable_read_node_t *) node, &token, value);
 
                     pm_node_destroy(parser, node);
@@ -15873,7 +15910,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                     pm_local_variable_read_node_t *cast = (pm_local_variable_read_node_t *) node;
                     parser_lex(parser);
 
-                    pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
+                    pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
                     pm_node_t *result = (pm_node_t *) pm_local_variable_and_write_node_create(parser, node, &token, value, cast->name, cast->depth);
 
                     pm_node_destroy(parser, node);
@@ -15894,7 +15931,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                             pm_parser_err_location(parser, &message_loc, PM_ERR_PARAMETER_NUMBERED_RESERVED);
                         }
 
-                        pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
+                        pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
                         pm_node_t *result = (pm_node_t *) pm_local_variable_and_write_node_create(parser, (pm_node_t *) cast, &token, value, constant_id, 0);
 
                         pm_node_destroy(parser, (pm_node_t *) cast);
@@ -15905,7 +15942,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                     // this is an aref expression, and we can transform it into
                     // an aset expression.
                     if (pm_call_node_index_p(cast)) {
-                        pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
+                        pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
                         return (pm_node_t *) pm_index_and_write_node_create(parser, cast, &token, value);
                     }
 
@@ -15917,7 +15954,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                     }
 
                     parse_call_operator_write(parser, cast, &token);
-                    pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
+                    pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
                     return (pm_node_t *) pm_call_and_write_node_create(parser, cast, &token, value);
                 }
                 case PM_MULTI_WRITE_NODE: {
@@ -15944,7 +15981,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                 case PM_GLOBAL_VARIABLE_READ_NODE: {
                     parser_lex(parser);
 
-                    pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
+                    pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
                     pm_node_t *result = (pm_node_t *) pm_global_variable_or_write_node_create(parser, node, &token, value);
 
                     pm_node_destroy(parser, node);
@@ -15953,7 +15990,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                 case PM_CLASS_VARIABLE_READ_NODE: {
                     parser_lex(parser);
 
-                    pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
+                    pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
                     pm_node_t *result = (pm_node_t *) pm_class_variable_or_write_node_create(parser, (pm_class_variable_read_node_t *) node, &token, value);
 
                     pm_node_destroy(parser, node);
@@ -15962,13 +15999,13 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                 case PM_CONSTANT_PATH_NODE: {
                     parser_lex(parser);
 
-                    pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
+                    pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
                     return (pm_node_t *) pm_constant_path_or_write_node_create(parser, (pm_constant_path_node_t *) node, &token, value);
                 }
                 case PM_CONSTANT_READ_NODE: {
                     parser_lex(parser);
 
-                    pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
+                    pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
                     pm_node_t *result = (pm_node_t *) pm_constant_or_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value);
 
                     pm_node_destroy(parser, node);
@@ -15977,7 +16014,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                 case PM_INSTANCE_VARIABLE_READ_NODE: {
                     parser_lex(parser);
 
-                    pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
+                    pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
                     pm_node_t *result = (pm_node_t *) pm_instance_variable_or_write_node_create(parser, (pm_instance_variable_read_node_t *) node, &token, value);
 
                     pm_node_destroy(parser, node);
@@ -15987,7 +16024,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                     pm_local_variable_read_node_t *cast = (pm_local_variable_read_node_t *) node;
                     parser_lex(parser);
 
-                    pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
+                    pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
                     pm_node_t *result = (pm_node_t *) pm_local_variable_or_write_node_create(parser, node, &token, value, cast->name, cast->depth);
 
                     pm_node_destroy(parser, node);
@@ -16008,7 +16045,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                             pm_parser_err_location(parser, &message_loc, PM_ERR_PARAMETER_NUMBERED_RESERVED);
                         }
 
-                        pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
+                        pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
                         pm_node_t *result = (pm_node_t *) pm_local_variable_or_write_node_create(parser, (pm_node_t *) cast, &token, value, constant_id, 0);
 
                         pm_node_destroy(parser, (pm_node_t *) cast);
@@ -16019,7 +16056,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                     // this is an aref expression, and we can transform it into
                     // an aset expression.
                     if (pm_call_node_index_p(cast)) {
-                        pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
+                        pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
                         return (pm_node_t *) pm_index_or_write_node_create(parser, cast, &token, value);
                     }
 
@@ -16031,7 +16068,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                     }
 
                     parse_call_operator_write(parser, cast, &token);
-                    pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
+                    pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
                     return (pm_node_t *) pm_call_or_write_node_create(parser, cast, &token, value);
                 }
                 case PM_MULTI_WRITE_NODE: {
@@ -16068,7 +16105,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                 case PM_GLOBAL_VARIABLE_READ_NODE: {
                     parser_lex(parser);
 
-                    pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+                    pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
                     pm_node_t *result = (pm_node_t *) pm_global_variable_operator_write_node_create(parser, node, &token, value);
 
                     pm_node_destroy(parser, node);
@@ -16077,7 +16114,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                 case PM_CLASS_VARIABLE_READ_NODE: {
                     parser_lex(parser);
 
-                    pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+                    pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
                     pm_node_t *result = (pm_node_t *) pm_class_variable_operator_write_node_create(parser, (pm_class_variable_read_node_t *) node, &token, value);
 
                     pm_node_destroy(parser, node);
@@ -16086,13 +16123,13 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                 case PM_CONSTANT_PATH_NODE: {
                     parser_lex(parser);
 
-                    pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+                    pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
                     return (pm_node_t *) pm_constant_path_operator_write_node_create(parser, (pm_constant_path_node_t *) node, &token, value);
                 }
                 case PM_CONSTANT_READ_NODE: {
                     parser_lex(parser);
 
-                    pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+                    pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
                     pm_node_t *result = (pm_node_t *) pm_constant_operator_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value);
 
                     pm_node_destroy(parser, node);
@@ -16101,7 +16138,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                 case PM_INSTANCE_VARIABLE_READ_NODE: {
                     parser_lex(parser);
 
-                    pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+                    pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
                     pm_node_t *result = (pm_node_t *) pm_instance_variable_operator_write_node_create(parser, (pm_instance_variable_read_node_t *) node, &token, value);
 
                     pm_node_destroy(parser, node);
@@ -16111,7 +16148,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                     pm_local_variable_read_node_t *cast = (pm_local_variable_read_node_t *) node;
                     parser_lex(parser);
 
-                    pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+                    pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
                     pm_node_t *result = (pm_node_t *) pm_local_variable_operator_write_node_create(parser, node, &token, value, cast->name, cast->depth);
 
                     pm_node_destroy(parser, node);
@@ -16132,7 +16169,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                             pm_parser_err_location(parser, &message_loc, PM_ERR_PARAMETER_NUMBERED_RESERVED);
                         }
 
-                        pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+                        pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
                         pm_node_t *result = (pm_node_t *) pm_local_variable_operator_write_node_create(parser, (pm_node_t *) cast, &token, value, constant_id, 0);
 
                         pm_node_destroy(parser, (pm_node_t *) cast);
@@ -16143,7 +16180,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                     // this is an aref expression, and we can transform it into
                     // an aset expression.
                     if (pm_call_node_index_p(cast)) {
-                        pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+                        pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
                         return (pm_node_t *) pm_index_operator_write_node_create(parser, cast, &token, value);
                     }
 
@@ -16155,7 +16192,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                     }
 
                     parse_call_operator_write(parser, cast, &token);
-                    pm_node_t *value = parse_expression(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+                    pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
                     return (pm_node_t *) pm_call_operator_write_node_create(parser, cast, &token, value);
                 }
                 case PM_MULTI_WRITE_NODE: {
diff --git a/src/regexp.c b/src/regexp.c
index 22833d17..cd52857f 100644
--- a/src/regexp.c
+++ b/src/regexp.c
@@ -116,18 +116,18 @@ pm_regexp_char_find(pm_regexp_parser_t *parser, uint8_t value) {
  * The properly track everything, we're going to build a little state machine.
  * It looks something like the following:
  *
- *                  ┌───────┐                 ┌─────────┐ ────────────┐
- * ──── lbrace ───> │ start │ ──── digit ───> │ minimum │             │
- *                  └───────┘                 └─────────┘ <─── digit ─┘
- *                      │                       │    │
- *   ┌───────┐          │                       │  rbrace
- *   │ comma │ <───── comma  ┌──── comma ───────┘    │
- *   └───────┘               V                       V
- *      │             ┌─────────┐               ┌─────────┐
- *      └── digit ──> │ maximum │ ── rbrace ──> │| final |│
- *                    └─────────┘               └─────────┘
- *                    │         ^
- *                    └─ digit ─┘
+ *                  +-------+                 +---------+ ------------+
+ * ---- lbrace ---> | start | ---- digit ---> | minimum |             |
+ *                  +-------+                 +---------+ <--- digit -+
+ *                      |                       |    |
+ *   +-------+          |                       |  rbrace
+ *   | comma | <----- comma  +---- comma -------+    |
+ *   +-------+               V                       V
+ *      |             +---------+               +---------+
+ *      +-- digit --> | maximum | -- rbrace --> || final ||
+ *                    +---------+               +---------+
+ *                    |         ^
+ *                    +- digit -+
  *
  * Note that by the time we've hit this function, the lbrace has already been
  * consumed so we're in the start state.
diff --git a/templates/lib/prism/serialize.rb.erb b/templates/lib/prism/serialize.rb.erb
index 28375045..e5a88ae9 100644
--- a/templates/lib/prism/serialize.rb.erb
+++ b/templates/lib/prism/serialize.rb.erb
@@ -73,12 +73,9 @@ module Prism
       end
 
       def load_encoding
-        Encoding.find(io.read(load_varint))
-      end
-
-      def load_force_encoding
-        @encoding = load_encoding
+        @encoding = Encoding.find(io.read(load_varint))
         @input = input.force_encoding(@encoding).freeze
+        @encoding
       end
 
       def load_start_line
@@ -121,10 +118,7 @@ module Prism
         encoding = load_encoding
         load_start_line
         comments, magic_comments, errors, warnings = load_metadata
-
-        if encoding != @encoding
-          tokens.each { |token,| token.value.force_encoding(encoding) }
-        end
+        tokens.each { |token,| token.value.force_encoding(encoding) }
 
         raise "Expected to consume all bytes while deserializing" unless @io.eof?
         Prism::ParseResult.new(tokens, comments, magic_comments, errors, warnings, @source)
@@ -132,7 +126,7 @@ module Prism
 
       def load_nodes
         load_header
-        load_force_encoding
+        load_encoding
         load_start_line
 
         comments, magic_comments, errors, warnings = load_metadata
diff --git a/templates/src/prettyprint.c.erb b/templates/src/prettyprint.c.erb
index d5bf196e..ed820d5e 100644
--- a/templates/src/prettyprint.c.erb
+++ b/templates/src/prettyprint.c.erb
@@ -1,3 +1,4 @@
+<%# encoding: ASCII -%>
 #include "prism/prettyprint.h"
 
 static void
@@ -63,29 +64,29 @@ prettyprint_node(pm_buffer_t *output_buffer, const pm_parser_t *parser, const pm
             prettyprint_location(output_buffer, parser, &node->location);
             pm_buffer_append_string(output_buffer, ")\n", 2);
             <%- node.fields.each_with_index do |field, index| -%>
-            <%- pointer, preadd = index == node.fields.length - 1 ? ["└── ", "    "] : ["├── ", "│   "] -%>
+            <%- pointer, preadd, preadd_bytesize = index == node.fields.length - 1 ? ["\\xe2\\x94\\x94\\xe2\\x94\\x80\\xe2\\x94\\x80 ", "    ", 4] : ["\\xe2\\x94\\x9c\\xe2\\x94\\x80\\xe2\\x94\\x80 ", "\\xe2\\x94\\x82   ", 6] -%>
 
             // <%= field.name %>
             {
                 pm_buffer_concat(output_buffer, prefix_buffer);
-                pm_buffer_append_string(output_buffer, "<%= pointer %><%= field.name %>:", <%= pointer.bytesize + field.name.length + 1 %>);
+                pm_buffer_append_string(output_buffer, "<%= pointer %><%= field.name %>:", <%= 10 + field.name.length + 1 %>);
             <%- case field -%>
             <%- when Prism::NodeField -%>
                 pm_buffer_append_byte(output_buffer, '\n');
 
                 size_t prefix_length = prefix_buffer->length;
-                pm_buffer_append_string(prefix_buffer, "<%= preadd %>", <%= preadd.bytesize %>);
+                pm_buffer_append_string(prefix_buffer, "<%= preadd %>", <%= preadd_bytesize %>);
                 pm_buffer_concat(output_buffer, prefix_buffer);
                 prettyprint_node(output_buffer, parser, (pm_node_t *) cast-><%= field.name %>, prefix_buffer);
                 prefix_buffer->length = prefix_length;
             <%- when Prism::OptionalNodeField -%>
                 if (cast-><%= field.name %> == NULL) {
-                    pm_buffer_append_string(output_buffer, " ∅\n", 5);
+                    pm_buffer_append_string(output_buffer, " \xe2\x88\x85\n", 5);
                 } else {
                     pm_buffer_append_byte(output_buffer, '\n');
 
                     size_t prefix_length = prefix_buffer->length;
-                    pm_buffer_append_string(prefix_buffer, "<%= preadd %>", <%= preadd.bytesize %>);
+                    pm_buffer_append_string(prefix_buffer, "<%= preadd %>", <%= preadd_bytesize %>);
                     pm_buffer_concat(output_buffer, prefix_buffer);
                     prettyprint_node(output_buffer, parser, (pm_node_t *) cast-><%= field.name %>, prefix_buffer);
                     prefix_buffer->length = prefix_length;
@@ -100,15 +101,15 @@ prettyprint_node(pm_buffer_t *output_buffer, const pm_parser_t *parser, const pm
                 size_t last_index = cast-><%= field.name %>.size;
                 for (uint32_t index = 0; index < last_index; index++) {
                     size_t prefix_length = prefix_buffer->length;
-                    pm_buffer_append_string(prefix_buffer, "<%= preadd %>", <%= preadd.bytesize %>);
+                    pm_buffer_append_string(prefix_buffer, "<%= preadd %>", <%= preadd_bytesize %>);
                     pm_buffer_concat(output_buffer, prefix_buffer);
 
                     if (index == last_index - 1) {
-                        pm_buffer_append_string(output_buffer, "└── ", 10);
+                        pm_buffer_append_string(output_buffer, "\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 ", 10);
                         pm_buffer_append_string(prefix_buffer, "    ", 4);
                     } else {
-                        pm_buffer_append_string(output_buffer, "├── ", 10);
-                        pm_buffer_append_string(prefix_buffer, "│   ", 6);
+                        pm_buffer_append_string(output_buffer, "\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 ", 10);
+                        pm_buffer_append_string(prefix_buffer, "\xe2\x94\x82   ", 6);
                     }
 
                     prettyprint_node(output_buffer, parser, (pm_node_t *) cast-><%= field.name %>.nodes[index], prefix_buffer);
@@ -120,7 +121,7 @@ prettyprint_node(pm_buffer_t *output_buffer, const pm_parser_t *parser, const pm
                 pm_buffer_append_byte(output_buffer, '\n');
             <%- when Prism::OptionalConstantField -%>
                 if (cast-><%= field.name %> == 0) {
-                    pm_buffer_append_string(output_buffer, " ∅\n", 5);
+                    pm_buffer_append_string(output_buffer, " \xe2\x88\x85\n", 5);
                 } else {
                     pm_buffer_append_byte(output_buffer, ' ');
                     prettyprint_constant(output_buffer, parser, cast-><%= field.name %>);
@@ -143,7 +144,7 @@ prettyprint_node(pm_buffer_t *output_buffer, const pm_parser_t *parser, const pm
             <%- when Prism::OptionalLocationField -%>
                 pm_location_t *location = &cast-><%= field.name %>;
                 if (location->start == NULL) {
-                    pm_buffer_append_string(output_buffer, " ∅\n", 5);
+                    pm_buffer_append_string(output_buffer, " \xe2\x88\x85\n", 5);
                 } else {
                     pm_buffer_append_byte(output_buffer, ' ');
                     prettyprint_location(output_buffer, parser, location);
@@ -163,7 +164,7 @@ prettyprint_node(pm_buffer_t *output_buffer, const pm_parser_t *parser, const pm
                     found = true;
                 }
                 <%- end -%>
-                if (!found) pm_buffer_append_string(output_buffer, " ∅", 4);
+                if (!found) pm_buffer_append_string(output_buffer, " \xe2\x88\x85", 4);
                 pm_buffer_append_byte(output_buffer, '\n');
             <%- else -%>
             <%- raise -%>
diff --git a/templates/template.rb b/templates/template.rb
index 777f8e51..204cb7a8 100755
--- a/templates/template.rb
+++ b/templates/template.rb
@@ -374,6 +374,14 @@ module Prism
       write_to ||= File.expand_path("../#{name}", __dir__)
       contents = heading + erb.result_with_hash(locals)
 
+      if (extension == ".c" || extension == ".h") && !contents.ascii_only?
+        # Enforce that we only have ASCII characters here. This is necessary
+        # for some locales that only allow ASCII characters in C source files.
+        contents.each_line.with_index(1) do |line, line_number|
+          raise "Non-ASCII character on line #{line_number} of #{write_to}" unless line.ascii_only?
+        end
+      end
+
       FileUtils.mkdir_p(File.dirname(write_to))
       File.write(write_to, contents)
     end


Test patch:
diff --git a/test/prism/encoding_test.rb b/test/prism/encoding_test.rb
index ce9a2fc9..a059e227 100644
--- a/test/prism/encoding_test.rb
+++ b/test/prism/encoding_test.rb
@@ -24,6 +24,7 @@ module Prism
       Encoding::IBM861 =>       0x00...0x100,
       Encoding::IBM862 =>       0x00...0x100,
       Encoding::IBM864 =>       0x00...0x100,
+      Encoding::IBM865 =>       0x00...0x100,
       Encoding::IBM866 =>       0x00...0x100,
       Encoding::ISO_8859_1 =>   0x00...0x100,
       Encoding::ISO_8859_2 =>   0x00...0x100,
@@ -42,6 +43,7 @@ module Prism
       Encoding::ISO_8859_16 =>  0x00...0x100,
       Encoding::KOI8_R =>       0x00...0x100,
       Encoding::MACCENTEURO =>  0x00...0x100,
+      Encoding::MACCROATIAN =>  0x00...0x100,
       Encoding::MACCYRILLIC =>  0x00...0x100,
       Encoding::MACGREEK =>     0x00...0x100,
       Encoding::MACICELAND =>   0x00...0x100,
diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb
index f13327c1..11036b1f 100644
--- a/test/prism/errors_test.rb
+++ b/test/prism/errors_test.rb
@@ -1682,6 +1682,20 @@ module Prism
       ]
     end
 
+    def test_argument_after_ellipsis
+      source = 'def foo(...); foo(..., 1); end'
+      assert_errors expression(source), source, [
+        ['Unexpected argument after `...`', 23..24]
+      ]
+    end
+
+    def test_ellipsis_in_no_paren_call
+      source = 'def foo(...); foo 1, ...; end'
+      assert_errors expression(source), source, [
+        ['Unexpected `...` in an non-parenthesized call', 21..24]
+      ]
+    end
+
     private
 
     def assert_errors(expected, source, errors, compare_ripper: RUBY_ENGINE == "ruby")
diff --git a/test/prism/ruby_api_test.rb b/test/prism/ruby_api_test.rb
index a61282cc..cd87a813 100644
--- a/test/prism/ruby_api_test.rb
+++ b/test/prism/ruby_api_test.rb
@@ -71,6 +71,38 @@ module Prism
       end
     end
 
+    def test_location_character_offsets
+      program = Prism.parse("😀 + 😀\n😍 ||= 😍").value
+
+      # first 😀
+      location = program.statements.body.first.receiver.location
+      assert_equal 0, location.start_character_offset
+      assert_equal 1, location.end_character_offset
+      assert_equal 0, location.start_character_column
+      assert_equal 1, location.end_character_column
+
+      # second 😀
+      location = program.statements.body.first.arguments.arguments.first.location
+      assert_equal 4, location.start_character_offset
+      assert_equal 5, location.end_character_offset
+      assert_equal 4, location.start_character_column
+      assert_equal 5, location.end_character_column
+
+      # first 😍
+      location = program.statements.body.last.name_loc
+      assert_equal 6, location.start_character_offset
+      assert_equal 7, location.end_character_offset
+      assert_equal 0, location.start_character_column
+      assert_equal 1, location.end_character_column
+
+      # second 😍
+      location = program.statements.body.last.value.location
+      assert_equal 12, location.start_character_offset
+      assert_equal 13, location.end_character_offset
+      assert_equal 6, location.start_character_column
+      assert_equal 7, location.end_character_column
+    end
+
     private
 
     def parse_expression(source)
diff --git a/test/prism/snapshots/rescue.txt b/test/prism/snapshots/rescue.txt
index 0574af2f..be411671 100644
--- a/test/prism/snapshots/rescue.txt
+++ b/test/prism/snapshots/rescue.txt
@@ -291,26 +291,26 @@
         │   ├── body:
         │   │   @ StatementsNode (location: (26,18)-(26,44))
         │   │   └── body: (length: 1)
-        │   │       └── @ CallNode (location: (26,18)-(26,44))
-        │   │           ├── receiver: ∅
-        │   │           ├── call_operator_loc: ∅
-        │   │           ├── message_loc: (26,18)-(26,30) = "other_method"
-        │   │           ├── opening_loc: ∅
-        │   │           ├── arguments:
-        │   │           │   @ ArgumentsNode (location: (26,31)-(26,44))
-        │   │           │   ├── arguments: (length: 1)
-        │   │           │   │   └── @ RescueModifierNode (location: (26,31)-(26,44))
-        │   │           │   │       ├── expression:
-        │   │           │   │       │   @ IntegerNode (location: (26,31)-(26,33))
-        │   │           │   │       │   └── flags: decimal
-        │   │           │   │       ├── keyword_loc: (26,34)-(26,40) = "rescue"
-        │   │           │   │       └── rescue_expression:
-        │   │           │   │           @ NilNode (location: (26,41)-(26,44))
-        │   │           │   └── flags: ∅
-        │   │           ├── closing_loc: ∅
-        │   │           ├── block: ∅
-        │   │           ├── flags: ∅
-        │   │           └── name: :other_method
+        │   │       └── @ RescueModifierNode (location: (26,18)-(26,44))
+        │   │           ├── expression:
+        │   │           │   @ CallNode (location: (26,18)-(26,33))
+        │   │           │   ├── receiver: ∅
+        │   │           │   ├── call_operator_loc: ∅
+        │   │           │   ├── message_loc: (26,18)-(26,30) = "other_method"
+        │   │           │   ├── opening_loc: ∅
+        │   │           │   ├── arguments:
+        │   │           │   │   @ ArgumentsNode (location: (26,31)-(26,33))
+        │   │           │   │   ├── arguments: (length: 1)
+        │   │           │   │   │   └── @ IntegerNode (location: (26,31)-(26,33))
+        │   │           │   │   │       └── flags: decimal
+        │   │           │   │   └── flags: ∅
+        │   │           │   ├── closing_loc: ∅
+        │   │           │   ├── block: ∅
+        │   │           │   ├── flags: ∅
+        │   │           │   └── name: :other_method
+        │   │           ├── keyword_loc: (26,34)-(26,40) = "rescue"
+        │   │           └── rescue_expression:
+        │   │               @ NilNode (location: (26,41)-(26,44))
         │   ├── locals: []
         │   ├── def_keyword_loc: (26,0)-(26,3) = "def"
         │   ├── operator_loc: ∅
diff --git a/test/prism/snapshots/seattlerb/defn_endless_command_rescue.txt b/test/prism/snapshots/seattlerb/defn_endless_command_rescue.txt
index c8ebc98a..ace1227a 100644
--- a/test/prism/snapshots/seattlerb/defn_endless_command_rescue.txt
+++ b/test/prism/snapshots/seattlerb/defn_endless_command_rescue.txt
@@ -11,27 +11,27 @@
             ├── body:
             │   @ StatementsNode (location: (1,18)-(1,43))
             │   └── body: (length: 1)
-            │       └── @ CallNode (location: (1,18)-(1,43))
-            │           ├── receiver: ∅
-            │           ├── call_operator_loc: ∅
-            │           ├── message_loc: (1,18)-(1,30) = "other_method"
-            │           ├── opening_loc: ∅
-            │           ├── arguments:
-            │           │   @ ArgumentsNode (location: (1,31)-(1,43))
-            │           │   ├── arguments: (length: 1)
-            │           │   │   └── @ RescueModifierNode (location: (1,31)-(1,43))
-            │           │   │       ├── expression:
-            │           │   │       │   @ IntegerNode (location: (1,31)-(1,33))
-            │           │   │       │   └── flags: decimal
-            │           │   │       ├── keyword_loc: (1,34)-(1,40) = "rescue"
-            │           │   │       └── rescue_expression:
-            │           │   │           @ IntegerNode (location: (1,41)-(1,43))
-            │           │   │           └── flags: decimal
-            │           │   └── flags: ∅
-            │           ├── closing_loc: ∅
-            │           ├── block: ∅
-            │           ├── flags: ∅
-            │           └── name: :other_method
+            │       └── @ RescueModifierNode (location: (1,18)-(1,43))
+            │           ├── expression:
+            │           │   @ CallNode (location: (1,18)-(1,33))
+            │           │   ├── receiver: ∅
+            │           │   ├── call_operator_loc: ∅
+            │           │   ├── message_loc: (1,18)-(1,30) = "other_method"
+            │           │   ├── opening_loc: ∅
+            │           │   ├── arguments:
+            │           │   │   @ ArgumentsNode (location: (1,31)-(1,33))
+            │           │   │   ├── arguments: (length: 1)
+            │           │   │   │   └── @ IntegerNode (location: (1,31)-(1,33))
+            │           │   │   │       └── flags: decimal
+            │           │   │   └── flags: ∅
+            │           │   ├── closing_loc: ∅
+            │           │   ├── block: ∅
+            │           │   ├── flags: ∅
+            │           │   └── name: :other_method
+            │           ├── keyword_loc: (1,34)-(1,40) = "rescue"
+            │           └── rescue_expression:
+            │               @ IntegerNode (location: (1,41)-(1,43))
+            │               └── flags: decimal
             ├── locals: []
             ├── def_keyword_loc: (1,0)-(1,3) = "def"
             ├── operator_loc: ∅
diff --git a/test/prism/snapshots/seattlerb/defs_endless_command_rescue.txt b/test/prism/snapshots/seattlerb/defs_endless_command_rescue.txt
index 26995bb9..cfa1d120 100644
--- a/test/prism/snapshots/seattlerb/defs_endless_command_rescue.txt
+++ b/test/prism/snapshots/seattlerb/defs_endless_command_rescue.txt
@@ -21,27 +21,27 @@
             ├── body:
             │   @ StatementsNode (location: (1,20)-(1,45))
             │   └── body: (length: 1)
-            │       └── @ CallNode (location: (1,20)-(1,45))
-            │           ├── receiver: ∅
-            │           ├── call_operator_loc: ∅
-            │           ├── message_loc: (1,20)-(1,32) = "other_method"
-            │           ├── opening_loc: ∅
-            │           ├── arguments:
-            │           │   @ ArgumentsNode (location: (1,33)-(1,45))
-            │           │   ├── arguments: (length: 1)
-            │           │   │   └── @ RescueModifierNode (location: (1,33)-(1,45))
-            │           │   │       ├── expression:
-            │           │   │       │   @ IntegerNode (location: (1,33)-(1,35))
-            │           │   │       │   └── flags: decimal
-            │           │   │       ├── keyword_loc: (1,36)-(1,42) = "rescue"
-            │           │   │       └── rescue_expression:
-            │           │   │           @ IntegerNode (location: (1,43)-(1,45))
-            │           │   │           └── flags: decimal
-            │           │   └── flags: ∅
-            │           ├── closing_loc: ∅
-            │           ├── block: ∅
-            │           ├── flags: ∅
-            │           └── name: :other_method
+            │       └── @ RescueModifierNode (location: (1,20)-(1,45))
+            │           ├── expression:
+            │           │   @ CallNode (location: (1,20)-(1,35))
+            │           │   ├── receiver: ∅
+            │           │   ├── call_operator_loc: ∅
+            │           │   ├── message_loc: (1,20)-(1,32) = "other_method"
+            │           │   ├── opening_loc: ∅
+            │           │   ├── arguments:
+            │           │   │   @ ArgumentsNode (location: (1,33)-(1,35))
+            │           │   │   ├── arguments: (length: 1)
+            │           │   │   │   └── @ IntegerNode (location: (1,33)-(1,35))
+            │           │   │   │       └── flags: decimal
+            │           │   │   └── flags: ∅
+            │           │   ├── closing_loc: ∅
+            │           │   ├── block: ∅
+            │           │   ├── flags: ∅
+            │           │   └── name: :other_method
+            │           ├── keyword_loc: (1,36)-(1,42) = "rescue"
+            │           └── rescue_expression:
+            │               @ IntegerNode (location: (1,43)-(1,45))
+            │               └── flags: decimal
             ├── locals: []
             ├── def_keyword_loc: (1,0)-(1,3) = "def"
             ├── operator_loc: (1,5)-(1,6) = "."
diff --git a/test/prism/snapshots/seattlerb/lasgn_call_nobracket_rescue_arg.txt b/test/prism/snapshots/seattlerb/lasgn_call_nobracket_rescue_arg.txt
index 44e5abb2..a54b70da 100644
--- a/test/prism/snapshots/seattlerb/lasgn_call_nobracket_rescue_arg.txt
+++ b/test/prism/snapshots/seattlerb/lasgn_call_nobracket_rescue_arg.txt
@@ -8,25 +8,25 @@
             ├── depth: 0
             ├── name_loc: (1,0)-(1,1) = "a"
             ├── value:
-            │   @ CallNode (location: (1,4)-(1,16))
-            │   ├── receiver: ∅
-            │   ├── call_operator_loc: ∅
-            │   ├── message_loc: (1,4)-(1,5) = "b"
-            │   ├── opening_loc: ∅
-            │   ├── arguments:
-            │   │   @ ArgumentsNode (location: (1,6)-(1,16))
-            │   │   ├── arguments: (length: 1)
-            │   │   │   └── @ RescueModifierNode (location: (1,6)-(1,16))
-            │   │   │       ├── expression:
-            │   │   │       │   @ IntegerNode (location: (1,6)-(1,7))
-            │   │   │       │   └── flags: decimal
-            │   │   │       ├── keyword_loc: (1,8)-(1,14) = "rescue"
-            │   │   │       └── rescue_expression:
-            │   │   │           @ IntegerNode (location: (1,15)-(1,16))
-            │   │   │           └── flags: decimal
-            │   │   └── flags: ∅
-            │   ├── closing_loc: ∅
-            │   ├── block: ∅
-            │   ├── flags: ∅
-            │   └── name: :b
+            │   @ RescueModifierNode (location: (1,4)-(1,16))
+            │   ├── expression:
+            │   │   @ CallNode (location: (1,4)-(1,7))
+            │   │   ├── receiver: ∅
+            │   │   ├── call_operator_loc: ∅
+            │   │   ├── message_loc: (1,4)-(1,5) = "b"
+            │   │   ├── opening_loc: ∅
+            │   │   ├── arguments:
+            │   │   │   @ ArgumentsNode (location: (1,6)-(1,7))
+            │   │   │   ├── arguments: (length: 1)
+            │   │   │   │   └── @ IntegerNode (location: (1,6)-(1,7))
+            │   │   │   │       └── flags: decimal
+            │   │   │   └── flags: ∅
+            │   │   ├── closing_loc: ∅
+            │   │   ├── block: ∅
+            │   │   ├── flags: ∅
+            │   │   └── name: :b
+            │   ├── keyword_loc: (1,8)-(1,14) = "rescue"
+            │   └── rescue_expression:
+            │       @ IntegerNode (location: (1,15)-(1,16))
+            │       └── flags: decimal
             └── operator_loc: (1,2)-(1,3) = "="
diff --git a/test/prism/snapshots/whitequark/endless_method_command_syntax.txt b/test/prism/snapshots/whitequark/endless_method_command_syntax.txt
index e04bfc43..b63bfe81 100644
--- a/test/prism/snapshots/whitequark/endless_method_command_syntax.txt
+++ b/test/prism/snapshots/whitequark/endless_method_command_syntax.txt
@@ -269,48 +269,48 @@
         │   ├── body:
         │   │   @ StatementsNode (location: (13,17)-(13,60))
         │   │   └── body: (length: 1)
-        │   │       └── @ CallNode (location: (13,17)-(13,60))
-        │   │           ├── receiver: ∅
-        │   │           ├── call_operator_loc: ∅
-        │   │           ├── message_loc: (13,17)-(13,22) = "raise"
-        │   │           ├── opening_loc: ∅
-        │   │           ├── arguments:
-        │   │           │   @ ArgumentsNode (location: (13,23)-(13,60))
-        │   │           │   ├── arguments: (length: 1)
-        │   │           │   │   └── @ RescueModifierNode (location: (13,23)-(13,60))
-        │   │           │   │       ├── expression:
-        │   │           │   │       │   @ StringNode (location: (13,23)-(13,37))
-        │   │           │   │       │   ├── flags: ∅
-        │   │           │   │       │   ├── opening_loc: (13,23)-(13,24) = "\""
-        │   │           │   │       │   ├── content_loc: (13,24)-(13,36) = "to be caught"
-        │   │           │   │       │   ├── closing_loc: (13,36)-(13,37) = "\""
-        │   │           │   │       │   └── unescaped: "to be caught"
-        │   │           │   │       ├── keyword_loc: (13,38)-(13,44) = "rescue"
-        │   │           │   │       └── rescue_expression:
-        │   │           │   │           @ InterpolatedStringNode (location: (13,45)-(13,60))
-        │   │           │   │           ├── opening_loc: (13,45)-(13,46) = "\""
-        │   │           │   │           ├── parts: (length: 2)
-        │   │           │   │           │   ├── @ StringNode (location: (13,46)-(13,55))
-        │   │           │   │           │   │   ├── flags: ∅
-        │   │           │   │           │   │   ├── opening_loc: ∅
-        │   │           │   │           │   │   ├── content_loc: (13,46)-(13,55) = "instance "
-        │   │           │   │           │   │   ├── closing_loc: ∅
-        │   │           │   │           │   │   └── unescaped: "instance "
-        │   │           │   │           │   └── @ EmbeddedStatementsNode (location: (13,55)-(13,59))
-        │   │           │   │           │       ├── opening_loc: (13,55)-(13,57) = "\#{"
-        │   │           │   │           │       ├── statements:
-        │   │           │   │           │       │   @ StatementsNode (location: (13,57)-(13,58))
-        │   │           │   │           │       │   └── body: (length: 1)
-        │   │           │   │           │       │       └── @ LocalVariableReadNode (location: (13,57)-(13,58))
-        │   │           │   │           │       │           ├── name: :x
-        │   │           │   │           │       │           └── depth: 0
-        │   │           │   │           │       └── closing_loc: (13,58)-(13,59) = "}"
-        │   │           │   │           └── closing_loc: (13,59)-(13,60) = "\""
-        │   │           │   └── flags: ∅
-        │   │           ├── closing_loc: ∅
-        │   │           ├── block: ∅
-        │   │           ├── flags: ∅
-        │   │           └── name: :raise
+        │   │       └── @ RescueModifierNode (location: (13,17)-(13,60))
+        │   │           ├── expression:
+        │   │           │   @ CallNode (location: (13,17)-(13,37))
+        │   │           │   ├── receiver: ∅
+        │   │           │   ├── call_operator_loc: ∅
+        │   │           │   ├── message_loc: (13,17)-(13,22) = "raise"
+        │   │           │   ├── opening_loc: ∅
+        │   │           │   ├── arguments:
+        │   │           │   │   @ ArgumentsNode (location: (13,23)-(13,37))
+        │   │           │   │   ├── arguments: (length: 1)
+        │   │           │   │   │   └── @ StringNode (location: (13,23)-(13,37))
+        │   │           │   │   │       ├── flags: ∅
+        │   │           │   │   │       ├── opening_loc: (13,23)-(13,24) = "\""
+        │   │           │   │   │       ├── content_loc: (13,24)-(13,36) = "to be caught"
+        │   │           │   │   │       ├── closing_loc: (13,36)-(13,37) = "\""
+        │   │           │   │   │       └── unescaped: "to be caught"
+        │   │           │   │   └── flags: ∅
+        │   │           │   ├── closing_loc: ∅
+        │   │           │   ├── block: ∅
+        │   │           │   ├── flags: ∅
+        │   │           │   └── name: :raise
+        │   │           ├── keyword_loc: (13,38)-(13,44) = "rescue"
+        │   │           └── rescue_expression:
+        │   │               @ InterpolatedStringNode (location: (13,45)-(13,60))
+        │   │               ├── opening_loc: (13,45)-(13,46) = "\""
+        │   │               ├── parts: (length: 2)
+        │   │               │   ├── @ StringNode (location: (13,46)-(13,55))
+        │   │               │   │   ├── flags: ∅
+        │   │               │   │   ├── opening_loc: ∅
+        │   │               │   │   ├── content_loc: (13,46)-(13,55) = "instance "
+        │   │               │   │   ├── closing_loc: ∅
+        │   │               │   │   └── unescaped: "instance "
+        │   │               │   └── @ EmbeddedStatementsNode (location: (13,55)-(13,59))
+        │   │               │       ├── opening_loc: (13,55)-(13,57) = "\#{"
+        │   │               │       ├── statements:
+        │   │               │       │   @ StatementsNode (location: (13,57)-(13,58))
+        │   │               │       │   └── body: (length: 1)
+        │   │               │       │       └── @ LocalVariableReadNode (location: (13,57)-(13,58))
+        │   │               │       │           ├── name: :x
+        │   │               │       │           └── depth: 0
+        │   │               │       └── closing_loc: (13,58)-(13,59) = "}"
+        │   │               └── closing_loc: (13,59)-(13,60) = "\""
         │   ├── locals: [:x]
         │   ├── def_keyword_loc: (13,0)-(13,3) = "def"
         │   ├── operator_loc: ∅
@@ -337,48 +337,48 @@
             ├── body:
             │   @ StatementsNode (location: (15,22)-(15,62))
             │   └── body: (length: 1)
-            │       └── @ CallNode (location: (15,22)-(15,62))
-            │           ├── receiver: ∅
-            │           ├── call_operator_loc: ∅
-            │           ├── message_loc: (15,22)-(15,27) = "raise"
-            │           ├── opening_loc: ∅
-            │           ├── arguments:
-            │           │   @ ArgumentsNode (location: (15,28)-(15,62))
-            │           │   ├── arguments: (length: 1)
-            │           │   │   └── @ RescueModifierNode (location: (15,28)-(15,62))
-            │           │   │       ├── expression:
-            │           │   │       │   @ StringNode (location: (15,28)-(15,42))
-            │           │   │       │   ├── flags: ∅
-            │           │   │       │   ├── opening_loc: (15,28)-(15,29) = "\""
-            │           │   │       │   ├── content_loc: (15,29)-(15,41) = "to be caught"
-            │           │   │       │   ├── closing_loc: (15,41)-(15,42) = "\""
-            │           │   │       │   └── unescaped: "to be caught"
-            │           │   │       ├── keyword_loc: (15,43)-(15,49) = "rescue"
-            │           │   │       └── rescue_expression:
-            │           │   │           @ InterpolatedStringNode (location: (15,50)-(15,62))
-            │           │   │           ├── opening_loc: (15,50)-(15,51) = "\""
-            │           │   │           ├── parts: (length: 2)
-            │           │   │           │   ├── @ StringNode (location: (15,51)-(15,57))
-            │           │   │           │   │   ├── flags: ∅
-            │           │   │           │   │   ├── opening_loc: ∅
-            │           │   │           │   │   ├── content_loc: (15,51)-(15,57) = "class "
-            │           │   │           │   │   ├── closing_loc: ∅
-            │           │   │           │   │   └── unescaped: "class "
-            │           │   │           │   └── @ EmbeddedStatementsNode (location: (15,57)-(15,61))
-            │           │   │           │       ├── opening_loc: (15,57)-(15,59) = "\#{"
-            │           │   │           │       ├── statements:
-            │           │   │           │       │   @ StatementsNode (location: (15,59)-(15,60))
-            │           │   │           │       │   └── body: (length: 1)
-            │           │   │           │       │       └── @ LocalVariableReadNode (location: (15,59)-(15,60))
-            │           │   │           │       │           ├── name: :x
-            │           │   │           │       │           └── depth: 0
-            │           │   │           │       └── closing_loc: (15,60)-(15,61) = "}"
-            │           │   │           └── closing_loc: (15,61)-(15,62) = "\""
-            │           │   └── flags: ∅
-            │           ├── closing_loc: ∅
-            │           ├── block: ∅
-            │           ├── flags: ∅
-            │           └── name: :raise
+            │       └── @ RescueModifierNode (location: (15,22)-(15,62))
+            │           ├── expression:
+            │           │   @ CallNode (location: (15,22)-(15,42))
+            │           │   ├── receiver: ∅
+            │           │   ├── call_operator_loc: ∅
+            │           │   ├── message_loc: (15,22)-(15,27) = "raise"
+            │           │   ├── opening_loc: ∅
+            │           │   ├── arguments:
+            │           │   │   @ ArgumentsNode (location: (15,28)-(15,42))
+            │           │   │   ├── arguments: (length: 1)
+            │           │   │   │   └── @ StringNode (location: (15,28)-(15,42))
+            │           │   │   │       ├── flags: ∅
+            │           │   │   │       ├── opening_loc: (15,28)-(15,29) = "\""
+            │           │   │   │       ├── content_loc: (15,29)-(15,41) = "to be caught"
+            │           │   │   │       ├── closing_loc: (15,41)-(15,42) = "\""
+            │           │   │   │       └── unescaped: "to be caught"
+            │           │   │   └── flags: ∅
+            │           │   ├── closing_loc: ∅
+            │           │   ├── block: ∅
+            │           │   ├── flags: ∅
+            │           │   └── name: :raise
+            │           ├── keyword_loc: (15,43)-(15,49) = "rescue"
+            │           └── rescue_expression:
+            │               @ InterpolatedStringNode (location: (15,50)-(15,62))
+            │               ├── opening_loc: (15,50)-(15,51) = "\""
+            │               ├── parts: (length: 2)
+            │               │   ├── @ StringNode (location: (15,51)-(15,57))
+            │               │   │   ├── flags: ∅
+            │               │   │   ├── opening_loc: ∅
+            │               │   │   ├── content_loc: (15,51)-(15,57) = "class "
+            │               │   │   ├── closing_loc: ∅
+            │               │   │   └── unescaped: "class "
+            │               │   └── @ EmbeddedStatementsNode (location: (15,57)-(15,61))
+            │               │       ├── opening_loc: (15,57)-(15,59) = "\#{"
+            │               │       ├── statements:
+            │               │       │   @ StatementsNode (location: (15,59)-(15,60))
+            │               │       │   └── body: (length: 1)
+            │               │       │       └── @ LocalVariableReadNode (location: (15,59)-(15,60))
+            │               │       │           ├── name: :x
+            │               │       │           └── depth: 0
+            │               │       └── closing_loc: (15,60)-(15,61) = "}"
+            │               └── closing_loc: (15,61)-(15,62) = "\""
             ├── locals: [:x]
             ├── def_keyword_loc: (15,0)-(15,3) = "def"
             ├── operator_loc: (15,8)-(15,9) = "."
diff --git a/test/prism/snapshots/whitequark/ruby_bug_12402.txt b/test/prism/snapshots/whitequark/ruby_bug_12402.txt
index 258f7b68..78a33693 100644
--- a/test/prism/snapshots/whitequark/ruby_bug_12402.txt
+++ b/test/prism/snapshots/whitequark/ruby_bug_12402.txt
@@ -7,34 +7,34 @@
         │   ├── name_loc: (1,0)-(1,3) = "foo"
         │   ├── operator_loc: (1,4)-(1,6) = "+="
         │   ├── value:
-        │   │   @ CallNode (location: (1,7)-(1,27))
-        │   │   ├── receiver: ∅
-        │   │   ├── call_operator_loc: ∅
-        │   │   ├── message_loc: (1,7)-(1,12) = "raise"
-        │   │   ├── opening_loc: ∅
-        │   │   ├── arguments:
-        │   │   │   @ ArgumentsNode (location: (1,13)-(1,27))
-        │   │   │   ├── arguments: (length: 1)
-        │   │   │   │   └── @ RescueModifierNode (location: (1,13)-(1,27))
-        │   │   │   │       ├── expression:
-        │   │   │   │       │   @ CallNode (location: (1,13)-(1,16))
-        │   │   │   │       │   ├── receiver: ∅
-        │   │   │   │       │   ├── call_operator_loc: ∅
-        │   │   │   │       │   ├── message_loc: (1,13)-(1,16) = "bar"
-        │   │   │   │       │   ├── opening_loc: ∅
-        │   │   │   │       │   ├── arguments: ∅
-        │   │   │   │       │   ├── closing_loc: ∅
-        │   │   │   │       │   ├── block: ∅
-        │   │   │   │       │   ├── flags: variable_call
-        │   │   │   │       │   └── name: :bar
-        │   │   │   │       ├── keyword_loc: (1,17)-(1,23) = "rescue"
-        │   │   │   │       └── rescue_expression:
-        │   │   │   │           @ NilNode (location: (1,24)-(1,27))
-        │   │   │   └── flags: ∅
-        │   │   ├── closing_loc: ∅
-        │   │   ├── block: ∅
-        │   │   ├── flags: ∅
-        │   │   └── name: :raise
+        │   │   @ RescueModifierNode (location: (1,7)-(1,27))
+        │   │   ├── expression:
+        │   │   │   @ CallNode (location: (1,7)-(1,16))
+        │   │   │   ├── receiver: ∅
+        │   │   │   ├── call_operator_loc: ∅
+        │   │   │   ├── message_loc: (1,7)-(1,12) = "raise"
+        │   │   │   ├── opening_loc: ∅
+        │   │   │   ├── arguments:
+        │   │   │   │   @ ArgumentsNode (location: (1,13)-(1,16))
+        │   │   │   │   ├── arguments: (length: 1)
+        │   │   │   │   │   └── @ CallNode (location: (1,13)-(1,16))
+        │   │   │   │   │       ├── receiver: ∅
+        │   │   │   │   │       ├── call_operator_loc: ∅
+        │   │   │   │   │       ├── message_loc: (1,13)-(1,16) = "bar"
+        │   │   │   │   │       ├── opening_loc: ∅
+        │   │   │   │   │       ├── arguments: ∅
+        │   │   │   │   │       ├── closing_loc: ∅
+        │   │   │   │   │       ├── block: ∅
+        │   │   │   │   │       ├── flags: variable_call
+        │   │   │   │   │       └── name: :bar
+        │   │   │   │   └── flags: ∅
+        │   │   │   ├── closing_loc: ∅
+        │   │   │   ├── block: ∅
+        │   │   │   ├── flags: ∅
+        │   │   │   └── name: :raise
+        │   │   ├── keyword_loc: (1,17)-(1,23) = "rescue"
+        │   │   └── rescue_expression:
+        │   │       @ NilNode (location: (1,24)-(1,27))
         │   ├── name: :foo
         │   ├── operator: :+
         │   └── depth: 0
@@ -78,34 +78,34 @@
         │   ├── depth: 0
         │   ├── name_loc: (5,0)-(5,3) = "foo"
         │   ├── value:
-        │   │   @ CallNode (location: (5,6)-(5,26))
-        │   │   ├── receiver: ∅
-        │   │   ├── call_operator_loc: ∅
-        │   │   ├── message_loc: (5,6)-(5,11) = "raise"
-        │   │   ├── opening_loc: ∅
-        │   │   ├── arguments:
-        │   │   │   @ ArgumentsNode (location: (5,12)-(5,26))
-        │   │   │   ├── arguments: (length: 1)
-        │   │   │   │   └── @ RescueModifierNode (location: (5,12)-(5,26))
-        │   │   │   │       ├── expression:
-        │   │   │   │       │   @ CallNode (location: (5,12)-(5,15))
-        │   │   │   │       │   ├── receiver: ∅
-        │   │   │   │       │   ├── call_operator_loc: ∅
-        │   │   │   │       │   ├── message_loc: (5,12)-(5,15) = "bar"
-        │   │   │   │       │   ├── opening_loc: ∅
-        │   │   │   │       │   ├── arguments: ∅
-        │   │   │   │       │   ├── closing_loc: ∅
-        │   │   │   │       │   ├── block: ∅
-        │   │   │   │       │   ├── flags: variable_call
-        │   │   │   │       │   └── name: :bar
-        │   │   │   │       ├── keyword_loc: (5,16)-(5,22) = "rescue"
-        │   │   │   │       └── rescue_expression:
-        │   │   │   │           @ NilNode (location: (5,23)-(5,26))
-        │   │   │   └── flags: ∅
-        │   │   ├── closing_loc: ∅
-        │   │   ├── block: ∅
-        │   │   ├── flags: ∅
-        │   │   └── name: :raise
+        │   │   @ RescueModifierNode (location: (5,6)-(5,26))
+        │   │   ├── expression:
+        │   │   │   @ CallNode (location: (5,6)-(5,15))
+        │   │   │   ├── receiver: ∅
+        │   │   │   ├── call_operator_loc: ∅
+        │   │   │   ├── message_loc: (5,6)-(5,11) = "raise"
+        │   │   │   ├── opening_loc: ∅
+        │   │   │   ├── arguments:
+        │   │   │   │   @ ArgumentsNode (location: (5,12)-(5,15))
+        │   │   │   │   ├── arguments: (length: 1)
+        │   │   │   │   │   └── @ CallNode (location: (5,12)-(5,15))
+        │   │   │   │   │       ├── receiver: ∅
+        │   │   │   │   │       ├── call_operator_loc: ∅
+        │   │   │   │   │       ├── message_loc: (5,12)-(5,15) = "bar"
+        │   │   │   │   │       ├── opening_loc: ∅
+        │   │   │   │   │       ├── arguments: ∅
+        │   │   │   │   │       ├── closing_loc: ∅
+        │   │   │   │   │       ├── block: ∅
+        │   │   │   │   │       ├── flags: variable_call
+        │   │   │   │   │       └── name: :bar
+        │   │   │   │   └── flags: ∅
+        │   │   │   ├── closing_loc: ∅
+        │   │   │   ├── block: ∅
+        │   │   │   ├── flags: ∅
+        │   │   │   └── name: :raise
+        │   │   ├── keyword_loc: (5,16)-(5,22) = "rescue"
+        │   │   └── rescue_expression:
+        │   │       @ NilNode (location: (5,23)-(5,26))
         │   └── operator_loc: (5,4)-(5,5) = "="
         ├── @ LocalVariableWriteNode (location: (7,0)-(7,27))
         │   ├── name: :foo
@@ -154,34 +154,34 @@
         │   ├── operator: :+
         │   ├── operator_loc: (9,6)-(9,8) = "+="
         │   └── value:
-        │       @ CallNode (location: (9,9)-(9,29))
-        │       ├── receiver: ∅
-        │       ├── call_operator_loc: ∅
-        │       ├── message_loc: (9,9)-(9,14) = "raise"
-        │       ├── opening_loc: ∅
-        │       ├── arguments:
-        │       │   @ ArgumentsNode (location: (9,15)-(9,29))
-        │       │   ├── arguments: (length: 1)
-        │       │   │   └── @ RescueModifierNode (location: (9,15)-(9,29))
-        │       │   │       ├── expression:
-        │       │   │       │   @ CallNode (location: (9,15)-(9,18))
-        │       │   │       │   ├── receiver: ∅
-        │       │   │       │   ├── call_operator_loc: ∅
-        │       │   │       │   ├── message_loc: (9,15)-(9,18) = "bar"
-        │       │   │       │   ├── opening_loc: ∅
-        │       │   │       │   ├── arguments: ∅
-        │       │   │       │   ├── closing_loc: ∅
-        │       │   │       │   ├── block: ∅
-        │       │   │       │   ├── flags: variable_call
-        │       │   │       │   └── name: :bar
-        │       │   │       ├── keyword_loc: (9,19)-(9,25) = "rescue"
-        │       │   │       └── rescue_expression:
-        │       │   │           @ NilNode (location: (9,26)-(9,29))
-        │       │   └── flags: ∅
-        │       ├── closing_loc: ∅
-        │       ├── block: ∅
-        │       ├── flags: ∅
-        │       └── name: :raise
+        │       @ RescueModifierNode (location: (9,9)-(9,29))
+        │       ├── expression:
+        │       │   @ CallNode (location: (9,9)-(9,18))
+        │       │   ├── receiver: ∅
+        │       │   ├── call_operator_loc: ∅
+        │       │   ├── message_loc: (9,9)-(9,14) = "raise"
+        │       │   ├── opening_loc: ∅
+        │       │   ├── arguments:
+        │       │   │   @ ArgumentsNode (location: (9,15)-(9,18))
+        │       │   │   ├── arguments: (length: 1)
+        │       │   │   │   └── @ CallNode (location: (9,15)-(9,18))
+        │       │   │   │       ├── receiver: ∅
+        │       │   │   │       ├── call_operator_loc: ∅
+        │       │   │   │       ├── message_loc: (9,15)-(9,18) = "bar"
+        │       │   │   │       ├── opening_loc: ∅
+        │       │   │   │       ├── arguments: ∅
+        │       │   │   │       ├── closing_loc: ∅
+        │       │   │   │       ├── block: ∅
+        │       │   │   │       ├── flags: variable_call
+        │       │   │   │       └── name: :bar
+        │       │   │   └── flags: ∅
+        │       │   ├── closing_loc: ∅
+        │       │   ├── block: ∅
+        │       │   ├── flags: ∅
+        │       │   └── name: :raise
+        │       ├── keyword_loc: (9,19)-(9,25) = "rescue"
+        │       └── rescue_expression:
+        │           @ NilNode (location: (9,26)-(9,29))
         ├── @ CallOperatorWriteNode (location: (11,0)-(11,30))
         │   ├── receiver:
         │   │   @ LocalVariableReadNode (location: (11,0)-(11,3))
@@ -236,34 +236,34 @@
         │   ├── operator: :+
         │   ├── operator_loc: (13,6)-(13,8) = "+="
         │   └── value:
-        │       @ CallNode (location: (13,9)-(13,29))
-        │       ├── receiver: ∅
-        │       ├── call_operator_loc: ∅
-        │       ├── message_loc: (13,9)-(13,14) = "raise"
-        │       ├── opening_loc: ∅
-        │       ├── arguments:
-        │       │   @ ArgumentsNode (location: (13,15)-(13,29))
-        │       │   ├── arguments: (length: 1)
-        │       │   │   └── @ RescueModifierNode (location: (13,15)-(13,29))
-        │       │   │       ├── expression:
-        │       │   │       │   @ CallNode (location: (13,15)-(13,18))
-        │       │   │       │   ├── receiver: ∅
-        │       │   │       │   ├── call_operator_loc: ∅
-        │       │   │       │   ├── message_loc: (13,15)-(13,18) = "bar"
-        │       │   │       │   ├── opening_loc: ∅
-        │       │   │       │   ├── arguments: ∅
-        │       │   │       │   ├── closing_loc: ∅
-        │       │   │       │   ├── block: ∅
-        │       │   │       │   ├── flags: variable_call
-        │       │   │       │   └── name: :bar
-        │       │   │       ├── keyword_loc: (13,19)-(13,25) = "rescue"
-        │       │   │       └── rescue_expression:
-        │       │   │           @ NilNode (location: (13,26)-(13,29))
-        │       │   └── flags: ∅
-        │       ├── closing_loc: ∅
-        │       ├── block: ∅
-        │       ├── flags: ∅
-        │       └── name: :raise
+        │       @ RescueModifierNode (location: (13,9)-(13,29))
+        │       ├── expression:
+        │       │   @ CallNode (location: (13,9)-(13,18))
+        │       │   ├── receiver: ∅
+        │       │   ├── call_operator_loc: ∅
+        │       │   ├── message_loc: (13,9)-(13,14) = "raise"
+        │       │   ├── opening_loc: ∅
+        │       │   ├── arguments:
+        │       │   │   @ ArgumentsNode (location: (13,15)-(13,18))
+        │       │   │   ├── arguments: (length: 1)
+        │       │   │   │   └── @ CallNode (location: (13,15)-(13,18))
+        │       │   │   │       ├── receiver: ∅
+        │       │   │   │       ├── call_operator_loc: ∅
+        │       │   │   │       ├── message_loc: (13,15)-(13,18) = "bar"
+        │       │   │   │       ├── opening_loc: ∅
+        │       │   │   │       ├── arguments: ∅
+        │       │   │   │       ├── closing_loc: ∅
+        │       │   │   │       ├── block: ∅
+        │       │   │   │       ├── flags: variable_call
+        │       │   │   │       └── name: :bar
+        │       │   │   └── flags: ∅
+        │       │   ├── closing_loc: ∅
+        │       │   ├── block: ∅
+        │       │   ├── flags: ∅
+        │       │   └── name: :raise
+        │       ├── keyword_loc: (13,19)-(13,25) = "rescue"
+        │       └── rescue_expression:
+        │           @ NilNode (location: (13,26)-(13,29))
         ├── @ CallOperatorWriteNode (location: (15,0)-(15,30))
         │   ├── receiver:
         │   │   @ LocalVariableReadNode (location: (15,0)-(15,3))
@@ -318,34 +318,34 @@
         │   │   └── delimiter_loc: (17,3)-(17,5) = "::"
         │   ├── operator_loc: (17,7)-(17,10) = "||="
         │   └── value:
-        │       @ CallNode (location: (17,11)-(17,31))
-        │       ├── receiver: ∅
-        │       ├── call_operator_loc: ∅
-        │       ├── message_loc: (17,11)-(17,16) = "raise"
-        │       ├── opening_loc: ∅
-        │       ├── arguments:
-        │       │   @ ArgumentsNode (location: (17,17)-(17,31))
-        │       │   ├── arguments: (length: 1)
-        │       │   │   └── @ RescueModifierNode (location: (17,17)-(17,31))
-        │       │   │       ├── expression:
-        │       │   │       │   @ CallNode (location: (17,17)-(17,20))
-        │       │   │       │   ├── receiver: ∅
-        │       │   │       │   ├── call_operator_loc: ∅
-        │       │   │       │   ├── message_loc: (17,17)-(17,20) = "bar"
-        │       │   │       │   ├── opening_loc: ∅
-        │       │   │       │   ├── arguments: ∅
-        │       │   │       │   ├── closing_loc: ∅
-        │       │   │       │   ├── block: ∅
-        │       │   │       │   ├── flags: variable_call
-        │       │   │       │   └── name: :bar
-        │       │   │       ├── keyword_loc: (17,21)-(17,27) = "rescue"
-        │       │   │       └── rescue_expression:
-        │       │   │           @ NilNode (location: (17,28)-(17,31))
-        │       │   └── flags: ∅
-        │       ├── closing_loc: ∅
-        │       ├── block: ∅
-        │       ├── flags: ∅
-        │       └── name: :raise
+        │       @ RescueModifierNode (location: (17,11)-(17,31))
+        │       ├── expression:
+        │       │   @ CallNode (location: (17,11)-(17,20))
+        │       │   ├── receiver: ∅
+        │       │   ├── call_operator_loc: ∅
+        │       │   ├── message_loc: (17,11)-(17,16) = "raise"
+        │       │   ├── opening_loc: ∅
+        │       │   ├── arguments:
+        │       │   │   @ ArgumentsNode (location: (17,17)-(17,20))
+        │       │   │   ├── arguments: (length: 1)
+        │       │   │   │   └── @ CallNode (location: (17,17)-(17,20))
+        │       │   │   │       ├── receiver: ∅
+        │       │   │   │       ├── call_operator_loc: ∅
+        │       │   │   │       ├── message_loc: (17,17)-(17,20) = "bar"
+        │       │   │   │       ├── opening_loc: ∅
+        │       │   │   │       ├── arguments: ∅
+        │       │   │   │       ├── closing_loc: ∅
+        │       │   │   │       ├── block: ∅
+        │       │   │   │       ├── flags: variable_call
+        │       │   │   │       └── name: :bar
+        │       │   │   └── flags: ∅
+        │       │   ├── closing_loc: ∅
+        │       │   ├── block: ∅
+        │       │   ├── flags: ∅
+        │       │   └── name: :raise
+        │       ├── keyword_loc: (17,21)-(17,27) = "rescue"
+        │       └── rescue_expression:
+        │           @ NilNode (location: (17,28)-(17,31))
         ├── @ ConstantPathOrWriteNode (location: (19,0)-(19,32))
         │   ├── target:
         │   │   @ ConstantPathNode (location: (19,0)-(19,6))
@@ -400,34 +400,34 @@
         │   ├── operator: :+
         │   ├── operator_loc: (21,7)-(21,9) = "+="
         │   └── value:
-        │       @ CallNode (location: (21,10)-(21,30))
-        │       ├── receiver: ∅
-        │       ├── call_operator_loc: ∅
-        │       ├── message_loc: (21,10)-(21,15) = "raise"
-        │       ├── opening_loc: ∅
-        │       ├── arguments:
-        │       │   @ ArgumentsNode (location: (21,16)-(21,30))
-        │       │   ├── arguments: (length: 1)
-        │       │   │   └── @ RescueModifierNode (location: (21,16)-(21,30))
-        │       │   │       ├── expression:
-        │       │   │       │   @ CallNode (location: (21,16)-(21,19))
-        │       │   │       │   ├── receiver: ∅
-        │       │   │       │   ├── call_operator_loc: ∅
-        │       │   │       │   ├── message_loc: (21,16)-(21,19) = "bar"
-        │       │   │       │   ├── opening_loc: ∅
-        │       │   │       │   ├── arguments: ∅
-        │       │   │       │   ├── closing_loc: ∅
-        │       │   │       │   ├── block: ∅
-        │       │   │       │   ├── flags: variable_call
-        │       │   │       │   └── name: :bar
-        │       │   │       ├── keyword_loc: (21,20)-(21,26) = "rescue"
-        │       │   │       └── rescue_expression:
-        │       │   │           @ NilNode (location: (21,27)-(21,30))
-        │       │   └── flags: ∅
-        │       ├── closing_loc: ∅
-        │       ├── block: ∅
-        │       ├── flags: ∅
-        │       └── name: :raise
+        │       @ RescueModifierNode (location: (21,10)-(21,30))
+        │       ├── expression:
+        │       │   @ CallNode (location: (21,10)-(21,19))
+        │       │   ├── receiver: ∅
+        │       │   ├── call_operator_loc: ∅
+        │       │   ├── message_loc: (21,10)-(21,15) = "raise"
+        │       │   ├── opening_loc: ∅
+        │       │   ├── arguments:
+        │       │   │   @ ArgumentsNode (location: (21,16)-(21,19))
+        │       │   │   ├── arguments: (length: 1)
+        │       │   │   │   └── @ CallNode (location: (21,16)-(21,19))
+        │       │   │   │       ├── receiver: ∅
+        │       │   │   │       ├── call_operator_loc: ∅
+        │       │   │   │       ├── message_loc: (21,16)-(21,19) = "bar"
+        │       │   │   │       ├── opening_loc: ∅
+        │       │   │   │       ├── arguments: ∅
+        │       │   │   │       ├── closing_loc: ∅
+        │       │   │   │       ├── block: ∅
+        │       │   │   │       ├── flags: variable_call
+        │       │   │   │       └── name: :bar
+        │       │   │   └── flags: ∅
+        │       │   ├── closing_loc: ∅
+        │       │   ├── block: ∅
+        │       │   ├── flags: ∅
+        │       │   └── name: :raise
+        │       ├── keyword_loc: (21,20)-(21,26) = "rescue"
+        │       └── rescue_expression:
+        │           @ NilNode (location: (21,27)-(21,30))
         ├── @ CallOperatorWriteNode (location: (23,0)-(23,31))
         │   ├── receiver:
         │   │   @ LocalVariableReadNode (location: (23,0)-(23,3))
@@ -488,34 +488,34 @@
         │   ├── operator: :+
         │   ├── operator_loc: (25,7)-(25,9) = "+="
         │   └── value:
-        │       @ CallNode (location: (25,10)-(25,30))
-        │       ├── receiver: ∅
-        │       ├── call_operator_loc: ∅
-        │       ├── message_loc: (25,10)-(25,15) = "raise"
-        │       ├── opening_loc: ∅
-        │       ├── arguments:
-        │       │   @ ArgumentsNode (location: (25,16)-(25,30))
-        │       │   ├── arguments: (length: 1)
-        │       │   │   └── @ RescueModifierNode (location: (25,16)-(25,30))
-        │       │   │       ├── expression:
-        │       │   │       │   @ CallNode (location: (25,16)-(25,19))
-        │       │   │       │   ├── receiver: ∅
-        │       │   │       │   ├── call_operator_loc: ∅
-        │       │   │       │   ├── message_loc: (25,16)-(25,19) = "bar"
-        │       │   │       │   ├── opening_loc: ∅
-        │       │   │       │   ├── arguments: ∅
-        │       │   │       │   ├── closing_loc: ∅
-        │       │   │       │   ├── block: ∅
-        │       │   │       │   ├── flags: variable_call
-        │       │   │       │   └── name: :bar
-        │       │   │       ├── keyword_loc: (25,20)-(25,26) = "rescue"
-        │       │   │       └── rescue_expression:
-        │       │   │           @ NilNode (location: (25,27)-(25,30))
-        │       │   └── flags: ∅
-        │       ├── closing_loc: ∅
-        │       ├── block: ∅
-        │       ├── flags: ∅
-        │       └── name: :raise
+        │       @ RescueModifierNode (location: (25,10)-(25,30))
+        │       ├── expression:
+        │       │   @ CallNode (location: (25,10)-(25,19))
+        │       │   ├── receiver: ∅
+        │       │   ├── call_operator_loc: ∅
+        │       │   ├── message_loc: (25,10)-(25,15) = "raise"
+        │       │   ├── opening_loc: ∅
+        │       │   ├── arguments:
+        │       │   │   @ ArgumentsNode (location: (25,16)-(25,19))
+        │       │   │   ├── arguments: (length: 1)
+        │       │   │   │   └── @ CallNode (location: (25,16)-(25,19))
+        │       │   │   │       ├── receiver: ∅
+        │       │   │   │       ├── call_operator_loc: ∅
+        │       │   │   │       ├── message_loc: (25,16)-(25,19) = "bar"
+        │       │   │   │       ├── opening_loc: ∅
+        │       │   │   │       ├── arguments: ∅
+        │       │   │   │       ├── closing_loc: ∅
+        │       │   │   │       ├── block: ∅
+        │       │   │   │       ├── flags: variable_call
+        │       │   │   │       └── name: :bar
+        │       │   │   └── flags: ∅
+        │       │   ├── closing_loc: ∅
+        │       │   ├── block: ∅
+        │       │   ├── flags: ∅
+        │       │   └── name: :raise
+        │       ├── keyword_loc: (25,20)-(25,26) = "rescue"
+        │       └── rescue_expression:
+        │           @ NilNode (location: (25,27)-(25,30))
         └── @ IndexOperatorWriteNode (location: (27,0)-(27,31))
             ├── receiver:
             │   @ LocalVariableReadNode (location: (27,0)-(27,3))