prism 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/src/prism.c CHANGED
@@ -544,10 +544,7 @@ pm_parser_warn_node(pm_parser_t *parser, const pm_node_t *node, pm_diagnostic_id
544
544
  * token.
545
545
  */
546
546
  static void
547
- pm_parser_err_heredoc_term(pm_parser_t *parser, pm_lex_mode_t *lex_mode) {
548
- const uint8_t *ident_start = lex_mode->as.heredoc.ident_start;
549
- size_t ident_length = lex_mode->as.heredoc.ident_length;
550
-
547
+ pm_parser_err_heredoc_term(pm_parser_t *parser, const uint8_t *ident_start, size_t ident_length) {
551
548
  PM_PARSER_ERR_FORMAT(
552
549
  parser,
553
550
  ident_start,
@@ -964,7 +961,7 @@ pm_locals_order(PRISM_ATTRIBUTE_UNUSED pm_parser_t *parser, pm_locals_t *locals,
964
961
  if (local->name != PM_CONSTANT_ID_UNSET) {
965
962
  pm_constant_id_list_insert(list, (size_t) local->index, local->name);
966
963
 
967
- if (warn_unused && local->reads == 0) {
964
+ if (warn_unused && local->reads == 0 && ((parser->start_line >= 0) || (pm_newline_list_line(&parser->newline_list, local->location.start, parser->start_line) >= 0))) {
968
965
  pm_constant_t *constant = pm_constant_pool_id_to_constant(&parser->constant_pool, local->name);
969
966
 
970
967
  if (constant->length >= 1 && *constant->start != '_') {
@@ -2110,14 +2107,6 @@ pm_array_node_create(pm_parser_t *parser, const pm_token_t *opening) {
2110
2107
  return node;
2111
2108
  }
2112
2109
 
2113
- /**
2114
- * Return the size of the given array node.
2115
- */
2116
- static inline size_t
2117
- pm_array_node_size(pm_array_node_t *node) {
2118
- return node->elements.size;
2119
- }
2120
-
2121
2110
  /**
2122
2111
  * Append an argument to an array node.
2123
2112
  */
@@ -8573,6 +8562,7 @@ context_terminator(pm_context_t context, pm_token_t *token) {
8573
8562
  case PM_CONTEXT_MAIN:
8574
8563
  case PM_CONTEXT_DEF_PARAMS:
8575
8564
  case PM_CONTEXT_DEFINED:
8565
+ case PM_CONTEXT_MULTI_TARGET:
8576
8566
  case PM_CONTEXT_TERNARY:
8577
8567
  case PM_CONTEXT_RESCUE_MODIFIER:
8578
8568
  return token->type == PM_TOKEN_EOF;
@@ -8777,6 +8767,7 @@ context_human(pm_context_t context) {
8777
8767
  case PM_CONTEXT_LOOP_PREDICATE: return "loop predicate";
8778
8768
  case PM_CONTEXT_MAIN: return "top level context";
8779
8769
  case PM_CONTEXT_MODULE: return "module definition";
8770
+ case PM_CONTEXT_MULTI_TARGET: return "multiple targets";
8780
8771
  case PM_CONTEXT_PARENS: return "parentheses";
8781
8772
  case PM_CONTEXT_POSTEXE: return "'END' block";
8782
8773
  case PM_CONTEXT_PREDICATE: return "predicate";
@@ -9051,6 +9042,10 @@ lex_global_variable(pm_parser_t *parser) {
9051
9042
  return PM_TOKEN_GLOBAL_VARIABLE;
9052
9043
  }
9053
9044
 
9045
+ // True if multiple characters are allowed after the declaration of the
9046
+ // global variable. Not true when it starts with "$-".
9047
+ bool allow_multiple = true;
9048
+
9054
9049
  switch (*parser->current.end) {
9055
9050
  case '~': // $~: match-data
9056
9051
  case '*': // $*: argv
@@ -9109,6 +9104,7 @@ lex_global_variable(pm_parser_t *parser) {
9109
9104
 
9110
9105
  case '-':
9111
9106
  parser->current.end++;
9107
+ allow_multiple = false;
9112
9108
  /* fallthrough */
9113
9109
  default: {
9114
9110
  size_t width;
@@ -9116,7 +9112,7 @@ lex_global_variable(pm_parser_t *parser) {
9116
9112
  if ((width = char_is_identifier(parser, parser->current.end)) > 0) {
9117
9113
  do {
9118
9114
  parser->current.end += width;
9119
- } while (parser->current.end < parser->end && (width = char_is_identifier(parser, parser->current.end)) > 0);
9115
+ } while (allow_multiple && parser->current.end < parser->end && (width = char_is_identifier(parser, parser->current.end)) > 0);
9120
9116
  } else if (pm_char_is_whitespace(peek(parser))) {
9121
9117
  // If we get here, then we have a $ followed by whitespace,
9122
9118
  // which is not allowed.
@@ -9881,6 +9877,10 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre
9881
9877
  }
9882
9878
  case 'c': {
9883
9879
  parser->current.end++;
9880
+ if (flags & PM_ESCAPE_FLAG_CONTROL) {
9881
+ pm_parser_err_current(parser, PM_ERR_ESCAPE_INVALID_CONTROL_REPEAT);
9882
+ }
9883
+
9884
9884
  if (parser->current.end == parser->end) {
9885
9885
  pm_parser_err_current(parser, PM_ERR_ESCAPE_INVALID_CONTROL);
9886
9886
  return;
@@ -9894,10 +9894,6 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre
9894
9894
  return;
9895
9895
  }
9896
9896
  case '\\':
9897
- if (flags & PM_ESCAPE_FLAG_CONTROL) {
9898
- pm_parser_err_current(parser, PM_ERR_ESCAPE_INVALID_CONTROL_REPEAT);
9899
- return;
9900
- }
9901
9897
  parser->current.end++;
9902
9898
 
9903
9899
  if (match(parser, 'u') || match(parser, 'U')) {
@@ -9931,6 +9927,10 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre
9931
9927
  }
9932
9928
  case 'C': {
9933
9929
  parser->current.end++;
9930
+ if (flags & PM_ESCAPE_FLAG_CONTROL) {
9931
+ pm_parser_err_current(parser, PM_ERR_ESCAPE_INVALID_CONTROL_REPEAT);
9932
+ }
9933
+
9934
9934
  if (peek(parser) != '-') {
9935
9935
  size_t width = parser->encoding->char_width(parser->current.end, parser->end - parser->current.end);
9936
9936
  pm_parser_err(parser, parser->current.start, parser->current.end + width, PM_ERR_ESCAPE_INVALID_CONTROL);
@@ -9951,10 +9951,6 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre
9951
9951
  return;
9952
9952
  }
9953
9953
  case '\\':
9954
- if (flags & PM_ESCAPE_FLAG_CONTROL) {
9955
- pm_parser_err_current(parser, PM_ERR_ESCAPE_INVALID_CONTROL_REPEAT);
9956
- return;
9957
- }
9958
9954
  parser->current.end++;
9959
9955
 
9960
9956
  if (match(parser, 'u') || match(parser, 'U')) {
@@ -9989,6 +9985,10 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre
9989
9985
  }
9990
9986
  case 'M': {
9991
9987
  parser->current.end++;
9988
+ if (flags & PM_ESCAPE_FLAG_META) {
9989
+ pm_parser_err_current(parser, PM_ERR_ESCAPE_INVALID_META_REPEAT);
9990
+ }
9991
+
9992
9992
  if (peek(parser) != '-') {
9993
9993
  size_t width = parser->encoding->char_width(parser->current.end, parser->end - parser->current.end);
9994
9994
  pm_parser_err(parser, parser->current.start, parser->current.end + width, PM_ERR_ESCAPE_INVALID_META);
@@ -10004,10 +10004,6 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre
10004
10004
  uint8_t peeked = peek(parser);
10005
10005
  switch (peeked) {
10006
10006
  case '\\':
10007
- if (flags & PM_ESCAPE_FLAG_META) {
10008
- pm_parser_err_current(parser, PM_ERR_ESCAPE_INVALID_META_REPEAT);
10009
- return;
10010
- }
10011
10007
  parser->current.end++;
10012
10008
 
10013
10009
  if (match(parser, 'u') || match(parser, 'U')) {
@@ -10050,6 +10046,8 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre
10050
10046
  default: {
10051
10047
  if (parser->current.end < parser->end) {
10052
10048
  escape_write_escape_encoded(parser, buffer);
10049
+ } else {
10050
+ pm_parser_err_current(parser, PM_ERR_INVALID_ESCAPE_CHARACTER);
10053
10051
  }
10054
10052
  return;
10055
10053
  }
@@ -11153,12 +11151,14 @@ parser_lex(pm_parser_t *parser) {
11153
11151
  lex_mode_push(parser, (pm_lex_mode_t) {
11154
11152
  .mode = PM_LEX_HEREDOC,
11155
11153
  .as.heredoc = {
11156
- .ident_start = ident_start,
11157
- .ident_length = ident_length,
11154
+ .base = {
11155
+ .ident_start = ident_start,
11156
+ .ident_length = ident_length,
11157
+ .quote = quote,
11158
+ .indent = indent
11159
+ },
11158
11160
  .next_start = parser->current.end,
11159
- .quote = quote,
11160
- .indent = indent,
11161
- .common_whitespace = (size_t) -1,
11161
+ .common_whitespace = NULL,
11162
11162
  .line_continuation = false
11163
11163
  }
11164
11164
  });
@@ -11171,7 +11171,7 @@ parser_lex(pm_parser_t *parser) {
11171
11171
  // this is not a valid heredoc declaration. In this case we
11172
11172
  // will add an error, but we will still return a heredoc
11173
11173
  // start.
11174
- if (!ident_error) pm_parser_err_heredoc_term(parser, parser->lex_modes.current);
11174
+ if (!ident_error) pm_parser_err_heredoc_term(parser, ident_start, ident_length);
11175
11175
  body_start = parser->end;
11176
11176
  } else {
11177
11177
  // Otherwise, we want to indicate that the body of the
@@ -12514,6 +12514,7 @@ parser_lex(pm_parser_t *parser) {
12514
12514
  // Now let's grab the information about the identifier off of the
12515
12515
  // current lex mode.
12516
12516
  pm_lex_mode_t *lex_mode = parser->lex_modes.current;
12517
+ pm_heredoc_lex_mode_t *heredoc_lex_mode = &lex_mode->as.heredoc.base;
12517
12518
 
12518
12519
  bool line_continuation = lex_mode->as.heredoc.line_continuation;
12519
12520
  lex_mode->as.heredoc.line_continuation = false;
@@ -12523,15 +12524,16 @@ parser_lex(pm_parser_t *parser) {
12523
12524
  // terminator) but still continue parsing so that content after the
12524
12525
  // declaration of the heredoc can be parsed.
12525
12526
  if (parser->current.end >= parser->end) {
12526
- pm_parser_err_heredoc_term(parser, lex_mode);
12527
+ pm_parser_err_heredoc_term(parser, heredoc_lex_mode->ident_start, heredoc_lex_mode->ident_length);
12527
12528
  parser->next_start = lex_mode->as.heredoc.next_start;
12528
12529
  parser->heredoc_end = parser->current.end;
12529
12530
  lex_state_set(parser, PM_LEX_STATE_END);
12531
+ lex_mode_pop(parser);
12530
12532
  LEX(PM_TOKEN_HEREDOC_END);
12531
12533
  }
12532
12534
 
12533
- const uint8_t *ident_start = lex_mode->as.heredoc.ident_start;
12534
- size_t ident_length = lex_mode->as.heredoc.ident_length;
12535
+ const uint8_t *ident_start = heredoc_lex_mode->ident_start;
12536
+ size_t ident_length = heredoc_lex_mode->ident_length;
12535
12537
 
12536
12538
  // If we are immediately following a newline and we have hit the
12537
12539
  // terminator, then we need to return the ending of the heredoc.
@@ -12556,10 +12558,7 @@ parser_lex(pm_parser_t *parser) {
12556
12558
  const uint8_t *terminator_start = ident_end - ident_length;
12557
12559
  const uint8_t *cursor = start;
12558
12560
 
12559
- if (
12560
- lex_mode->as.heredoc.indent == PM_HEREDOC_INDENT_DASH ||
12561
- lex_mode->as.heredoc.indent == PM_HEREDOC_INDENT_TILDE
12562
- ) {
12561
+ if (heredoc_lex_mode->indent == PM_HEREDOC_INDENT_DASH || heredoc_lex_mode->indent == PM_HEREDOC_INDENT_TILDE) {
12563
12562
  while (cursor < terminator_start && pm_char_is_inline_whitespace(*cursor)) {
12564
12563
  cursor++;
12565
12564
  }
@@ -12582,17 +12581,19 @@ parser_lex(pm_parser_t *parser) {
12582
12581
  }
12583
12582
 
12584
12583
  lex_state_set(parser, PM_LEX_STATE_END);
12584
+ lex_mode_pop(parser);
12585
12585
  LEX(PM_TOKEN_HEREDOC_END);
12586
12586
  }
12587
12587
  }
12588
12588
 
12589
- size_t whitespace = pm_heredoc_strspn_inline_whitespace(parser, &start, lex_mode->as.heredoc.indent);
12589
+ size_t whitespace = pm_heredoc_strspn_inline_whitespace(parser, &start, heredoc_lex_mode->indent);
12590
12590
  if (
12591
- lex_mode->as.heredoc.indent == PM_HEREDOC_INDENT_TILDE &&
12592
- (lex_mode->as.heredoc.common_whitespace > whitespace) &&
12591
+ heredoc_lex_mode->indent == PM_HEREDOC_INDENT_TILDE &&
12592
+ lex_mode->as.heredoc.common_whitespace != NULL &&
12593
+ (*lex_mode->as.heredoc.common_whitespace > whitespace) &&
12593
12594
  peek_at(parser, start) != '\n'
12594
12595
  ) {
12595
- lex_mode->as.heredoc.common_whitespace = whitespace;
12596
+ *lex_mode->as.heredoc.common_whitespace = whitespace;
12596
12597
  }
12597
12598
  }
12598
12599
 
@@ -12601,7 +12602,7 @@ parser_lex(pm_parser_t *parser) {
12601
12602
  // strpbrk to find the first of these characters.
12602
12603
  uint8_t breakpoints[] = "\r\n\\#";
12603
12604
 
12604
- pm_heredoc_quote_t quote = lex_mode->as.heredoc.quote;
12605
+ pm_heredoc_quote_t quote = heredoc_lex_mode->quote;
12605
12606
  if (quote == PM_HEREDOC_QUOTE_SINGLE) {
12606
12607
  breakpoints[3] = '\0';
12607
12608
  }
@@ -12664,8 +12665,7 @@ parser_lex(pm_parser_t *parser) {
12664
12665
  // leading whitespace if we have a - or ~ heredoc.
12665
12666
  const uint8_t *cursor = start;
12666
12667
 
12667
- if (lex_mode->as.heredoc.indent == PM_HEREDOC_INDENT_DASH ||
12668
- lex_mode->as.heredoc.indent == PM_HEREDOC_INDENT_TILDE) {
12668
+ if (heredoc_lex_mode->indent == PM_HEREDOC_INDENT_DASH || heredoc_lex_mode->indent == PM_HEREDOC_INDENT_TILDE) {
12669
12669
  while (cursor < terminator_start && pm_char_is_inline_whitespace(*cursor)) {
12670
12670
  cursor++;
12671
12671
  }
@@ -12681,16 +12681,16 @@ parser_lex(pm_parser_t *parser) {
12681
12681
  }
12682
12682
  }
12683
12683
 
12684
- size_t whitespace = pm_heredoc_strspn_inline_whitespace(parser, &start, lex_mode->as.heredoc.indent);
12684
+ size_t whitespace = pm_heredoc_strspn_inline_whitespace(parser, &start, lex_mode->as.heredoc.base.indent);
12685
12685
 
12686
12686
  // If we have hit a newline that is followed by a valid
12687
12687
  // terminator, then we need to return the content of the
12688
12688
  // heredoc here as string content. Then, the next time a
12689
12689
  // token is lexed, it will match again and return the
12690
12690
  // end of the heredoc.
12691
- if (lex_mode->as.heredoc.indent == PM_HEREDOC_INDENT_TILDE) {
12692
- if ((lex_mode->as.heredoc.common_whitespace > whitespace) && peek_at(parser, start) != '\n') {
12693
- lex_mode->as.heredoc.common_whitespace = whitespace;
12691
+ if (lex_mode->as.heredoc.base.indent == PM_HEREDOC_INDENT_TILDE) {
12692
+ if ((lex_mode->as.heredoc.common_whitespace != NULL) && (*lex_mode->as.heredoc.common_whitespace > whitespace) && peek_at(parser, start) != '\n') {
12693
+ *lex_mode->as.heredoc.common_whitespace = whitespace;
12694
12694
  }
12695
12695
 
12696
12696
  parser->current.end = breakpoint + 1;
@@ -12757,7 +12757,7 @@ parser_lex(pm_parser_t *parser) {
12757
12757
  // If we are in a tilde here, we should
12758
12758
  // break out of the loop and return the
12759
12759
  // string content.
12760
- if (lex_mode->as.heredoc.indent == PM_HEREDOC_INDENT_TILDE) {
12760
+ if (heredoc_lex_mode->indent == PM_HEREDOC_INDENT_TILDE) {
12761
12761
  const uint8_t *end = parser->current.end;
12762
12762
  pm_newline_list_append(&parser->newline_list, end);
12763
12763
 
@@ -12983,7 +12983,7 @@ pm_binding_powers_t pm_binding_powers[PM_TOKEN_MAXIMUM] = {
12983
12983
  [PM_TOKEN_PERCENT] = LEFT_ASSOCIATIVE(PM_BINDING_POWER_FACTOR),
12984
12984
  [PM_TOKEN_SLASH] = LEFT_ASSOCIATIVE(PM_BINDING_POWER_FACTOR),
12985
12985
  [PM_TOKEN_STAR] = LEFT_ASSOCIATIVE(PM_BINDING_POWER_FACTOR),
12986
- [PM_TOKEN_USTAR] = LEFT_ASSOCIATIVE(PM_BINDING_POWER_FACTOR),
12986
+ [PM_TOKEN_USTAR] = RIGHT_ASSOCIATIVE_UNARY(PM_BINDING_POWER_FACTOR),
12987
12987
 
12988
12988
  // -@
12989
12989
  [PM_TOKEN_UMINUS] = RIGHT_ASSOCIATIVE_UNARY(PM_BINDING_POWER_UMINUS),
@@ -13165,13 +13165,11 @@ expect3(pm_parser_t *parser, pm_token_type_t type1, pm_token_type_t type2, pm_to
13165
13165
  * lex mode accordingly.
13166
13166
  */
13167
13167
  static void
13168
- expect1_heredoc_term(pm_parser_t *parser, pm_lex_mode_t *lex_mode) {
13168
+ expect1_heredoc_term(pm_parser_t *parser, const uint8_t *ident_start, size_t ident_length) {
13169
13169
  if (match1(parser, PM_TOKEN_HEREDOC_END)) {
13170
- lex_mode_pop(parser);
13171
13170
  parser_lex(parser);
13172
13171
  } else {
13173
- pm_parser_err_heredoc_term(parser, lex_mode);
13174
- lex_mode_pop(parser);
13172
+ pm_parser_err_heredoc_term(parser, ident_start, ident_length);
13175
13173
  parser->previous.start = parser->previous.end;
13176
13174
  parser->previous.type = PM_TOKEN_MISSING;
13177
13175
  }
@@ -13797,6 +13795,13 @@ parse_targets(pm_parser_t *parser, pm_node_t *first_target, pm_binding_power_t b
13797
13795
  pm_node_t *splat = (pm_node_t *) pm_splat_node_create(parser, &star_operator, name);
13798
13796
  pm_multi_target_node_targets_append(parser, result, splat);
13799
13797
  has_rest = true;
13798
+ } else if (match1(parser, PM_TOKEN_PARENTHESIS_LEFT)) {
13799
+ context_push(parser, PM_CONTEXT_MULTI_TARGET);
13800
+ pm_node_t *target = parse_expression(parser, binding_power, false, false, PM_ERR_EXPECT_EXPRESSION_AFTER_COMMA, (uint16_t) (depth + 1));
13801
+ target = parse_target(parser, target, true, false);
13802
+
13803
+ pm_multi_target_node_targets_append(parser, result, target);
13804
+ context_pop(parser);
13800
13805
  } else if (token_begins_expression_p(parser->current.type)) {
13801
13806
  pm_node_t *target = parse_expression(parser, binding_power, false, false, PM_ERR_EXPECT_EXPRESSION_AFTER_COMMA, (uint16_t) (depth + 1));
13802
13807
  target = parse_target(parser, target, true, false);
@@ -14108,8 +14113,8 @@ static void
14108
14113
  parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_forwarding, pm_token_type_t terminator, uint16_t depth) {
14109
14114
  pm_binding_power_t binding_power = pm_binding_powers[parser->current.type].left;
14110
14115
 
14111
- // First we need to check if the next token is one that could be the start of
14112
- // an argument. If it's not, then we can just return.
14116
+ // First we need to check if the next token is one that could be the start
14117
+ // of an argument. If it's not, then we can just return.
14113
14118
  if (
14114
14119
  match2(parser, terminator, PM_TOKEN_EOF) ||
14115
14120
  (binding_power != PM_BINDING_POWER_UNSET && binding_power < PM_BINDING_POWER_RANGE) ||
@@ -14297,23 +14302,32 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for
14297
14302
  // If parsing the argument failed, we need to stop parsing arguments.
14298
14303
  if (PM_NODE_TYPE_P(argument, PM_MISSING_NODE) || parser->recovering) break;
14299
14304
 
14300
- // If the terminator of these arguments is not EOF, then we have a specific
14301
- // token we're looking for. In that case we can accept a newline here
14302
- // because it is not functioning as a statement terminator.
14303
- if (terminator != PM_TOKEN_EOF) accept1(parser, PM_TOKEN_NEWLINE);
14305
+ // If the terminator of these arguments is not EOF, then we have a
14306
+ // specific token we're looking for. In that case we can accept a
14307
+ // newline here because it is not functioning as a statement terminator.
14308
+ bool accepted_newline = false;
14309
+ if (terminator != PM_TOKEN_EOF) {
14310
+ accepted_newline = accept1(parser, PM_TOKEN_NEWLINE);
14311
+ }
14304
14312
 
14305
14313
  if (parser->previous.type == PM_TOKEN_COMMA && parsed_bare_hash) {
14306
- // If we previously were on a comma and we just parsed a bare hash, then
14307
- // we want to continue parsing arguments. This is because the comma was
14308
- // grabbed up by the hash parser.
14314
+ // If we previously were on a comma and we just parsed a bare hash,
14315
+ // then we want to continue parsing arguments. This is because the
14316
+ // comma was grabbed up by the hash parser.
14317
+ } else if (accept1(parser, PM_TOKEN_COMMA)) {
14318
+ // If there was a comma, then we need to check if we also accepted a
14319
+ // newline. If we did, then this is a syntax error.
14320
+ if (accepted_newline) {
14321
+ pm_parser_err_previous(parser, PM_ERR_INVALID_COMMA);
14322
+ }
14309
14323
  } else {
14310
- // If there is no comma at the end of the argument list then we're done
14311
- // parsing arguments and can break out of this loop.
14312
- if (!accept1(parser, PM_TOKEN_COMMA)) break;
14324
+ // If there is no comma at the end of the argument list then we're
14325
+ // done parsing arguments and can break out of this loop.
14326
+ break;
14313
14327
  }
14314
14328
 
14315
- // If we hit the terminator, then that means we have a trailing comma so we
14316
- // can accept that output as well.
14329
+ // If we hit the terminator, then that means we have a trailing comma so
14330
+ // we can accept that output as well.
14317
14331
  if (match1(parser, terminator)) break;
14318
14332
  }
14319
14333
  }
@@ -14470,13 +14484,14 @@ parse_parameters(
14470
14484
  bool accepts_blocks_in_defaults,
14471
14485
  uint16_t depth
14472
14486
  ) {
14473
- pm_parameters_node_t *params = pm_parameters_node_create(parser);
14474
- bool looping = true;
14475
-
14476
14487
  pm_do_loop_stack_push(parser, false);
14488
+
14489
+ pm_parameters_node_t *params = pm_parameters_node_create(parser);
14477
14490
  pm_parameters_order_t order = PM_PARAMETERS_ORDER_NONE;
14478
14491
 
14479
- do {
14492
+ while (true) {
14493
+ bool parsing = true;
14494
+
14480
14495
  switch (parser->current.type) {
14481
14496
  case PM_TOKEN_PARENTHESIS_LEFT: {
14482
14497
  update_parameter_state(parser, &parser->current, &order);
@@ -14611,7 +14626,7 @@ parse_parameters(
14611
14626
  // then we can put a missing node in its place and stop parsing the
14612
14627
  // parameters entirely now.
14613
14628
  if (parser->recovering) {
14614
- looping = false;
14629
+ parsing = false;
14615
14630
  break;
14616
14631
  }
14617
14632
  } else if (order > PM_PARAMETERS_ORDER_AFTER_OPTIONAL) {
@@ -14669,7 +14684,7 @@ parse_parameters(
14669
14684
  context_pop(parser);
14670
14685
 
14671
14686
  if (uses_parentheses) {
14672
- looping = false;
14687
+ parsing = false;
14673
14688
  break;
14674
14689
  }
14675
14690
 
@@ -14713,7 +14728,7 @@ parse_parameters(
14713
14728
  // then we can put a missing node in its place and stop parsing the
14714
14729
  // parameters entirely now.
14715
14730
  if (parser->recovering) {
14716
- looping = false;
14731
+ parsing = false;
14717
14732
  break;
14718
14733
  }
14719
14734
  }
@@ -14815,14 +14830,31 @@ parse_parameters(
14815
14830
  }
14816
14831
  }
14817
14832
 
14818
- looping = false;
14833
+ parsing = false;
14819
14834
  break;
14820
14835
  }
14821
14836
 
14822
- if (looping && uses_parentheses) {
14823
- accept1(parser, PM_TOKEN_NEWLINE);
14837
+ // If we hit some kind of issue while parsing the parameter, this would
14838
+ // have been set to false. In that case, we need to break out of the
14839
+ // loop.
14840
+ if (!parsing) break;
14841
+
14842
+ bool accepted_newline = false;
14843
+ if (uses_parentheses) {
14844
+ accepted_newline = accept1(parser, PM_TOKEN_NEWLINE);
14824
14845
  }
14825
- } while (looping && accept1(parser, PM_TOKEN_COMMA));
14846
+
14847
+ if (accept1(parser, PM_TOKEN_COMMA)) {
14848
+ // If there was a comma, but we also accepted a newline, then this
14849
+ // is a syntax error.
14850
+ if (accepted_newline) {
14851
+ pm_parser_err_previous(parser, PM_ERR_INVALID_COMMA);
14852
+ }
14853
+ } else {
14854
+ // If there was no comma, then we're done parsing parameters.
14855
+ break;
14856
+ }
14857
+ }
14826
14858
 
14827
14859
  pm_do_loop_stack_pop(parser);
14828
14860
 
@@ -15500,6 +15532,7 @@ parse_return(pm_parser_t *parser, pm_node_t *node) {
15500
15532
  case PM_CONTEXT_IF:
15501
15533
  case PM_CONTEXT_LOOP_PREDICATE:
15502
15534
  case PM_CONTEXT_MAIN:
15535
+ case PM_CONTEXT_MULTI_TARGET:
15503
15536
  case PM_CONTEXT_PARENS:
15504
15537
  case PM_CONTEXT_POSTEXE:
15505
15538
  case PM_CONTEXT_PREDICATE:
@@ -15628,6 +15661,7 @@ parse_block_exit(pm_parser_t *parser, pm_node_t *node) {
15628
15661
  case PM_CONTEXT_MODULE_ENSURE:
15629
15662
  case PM_CONTEXT_MODULE_RESCUE:
15630
15663
  case PM_CONTEXT_MODULE:
15664
+ case PM_CONTEXT_MULTI_TARGET:
15631
15665
  case PM_CONTEXT_PARENS:
15632
15666
  case PM_CONTEXT_PREDICATE:
15633
15667
  case PM_CONTEXT_RESCUE_MODIFIER:
@@ -17045,7 +17079,7 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node
17045
17079
  parse_pattern_hash_key(parser, &keys, first_node);
17046
17080
  pm_node_t *value;
17047
17081
 
17048
- if (match7(parser, PM_TOKEN_COMMA, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON)) {
17082
+ if (match8(parser, PM_TOKEN_COMMA, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON, PM_TOKEN_EOF)) {
17049
17083
  // Otherwise, we will create an implicit local variable
17050
17084
  // target for the value.
17051
17085
  value = parse_pattern_hash_implicit_value(parser, captures, (pm_symbol_node_t *) first_node);
@@ -17082,7 +17116,12 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node
17082
17116
  // If there are any other assocs, then we'll parse them now.
17083
17117
  while (accept1(parser, PM_TOKEN_COMMA)) {
17084
17118
  // Here we need to break to support trailing commas.
17085
- if (match6(parser, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON)) {
17119
+ if (match7(parser, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON, PM_TOKEN_EOF)) {
17120
+ // Trailing commas are not allowed to follow a rest pattern.
17121
+ if (rest != NULL) {
17122
+ pm_parser_err_token(parser, &parser->current, PM_ERR_PATTERN_EXPRESSION_AFTER_REST);
17123
+ }
17124
+
17086
17125
  break;
17087
17126
  }
17088
17127
 
@@ -17575,9 +17614,10 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_list_t *captures, uint8_t flag
17575
17614
  // Gather up all of the patterns into the list.
17576
17615
  while (accept1(parser, PM_TOKEN_COMMA)) {
17577
17616
  // Break early here in case we have a trailing comma.
17578
- if (match4(parser, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_SEMICOLON)) {
17617
+ if (match6(parser, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_SEMICOLON, PM_TOKEN_NEWLINE, PM_TOKEN_EOF)) {
17579
17618
  node = (pm_node_t *) pm_implicit_rest_node_create(parser, &parser->previous);
17580
17619
  pm_node_list_append(&nodes, node);
17620
+ trailing_rest = true;
17581
17621
  break;
17582
17622
  }
17583
17623
 
@@ -17779,6 +17819,7 @@ parse_retry(pm_parser_t *parser, const pm_node_t *node) {
17779
17819
  case PM_CONTEXT_LAMBDA_BRACES:
17780
17820
  case PM_CONTEXT_LAMBDA_DO_END:
17781
17821
  case PM_CONTEXT_LOOP_PREDICATE:
17822
+ case PM_CONTEXT_MULTI_TARGET:
17782
17823
  case PM_CONTEXT_PARENS:
17783
17824
  case PM_CONTEXT_POSTEXE:
17784
17825
  case PM_CONTEXT_PREDICATE:
@@ -17862,6 +17903,7 @@ parse_yield(pm_parser_t *parser, const pm_node_t *node) {
17862
17903
  case PM_CONTEXT_LAMBDA_ENSURE:
17863
17904
  case PM_CONTEXT_LAMBDA_RESCUE:
17864
17905
  case PM_CONTEXT_LOOP_PREDICATE:
17906
+ case PM_CONTEXT_MULTI_TARGET:
17865
17907
  case PM_CONTEXT_PARENS:
17866
17908
  case PM_CONTEXT_POSTEXE:
17867
17909
  case PM_CONTEXT_PREDICATE:
@@ -17951,19 +17993,31 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
17951
17993
  bool parsed_bare_hash = false;
17952
17994
 
17953
17995
  while (!match2(parser, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_EOF)) {
17996
+ bool accepted_newline = accept1(parser, PM_TOKEN_NEWLINE);
17997
+
17954
17998
  // Handle the case where we don't have a comma and we have a
17955
17999
  // newline followed by a right bracket.
17956
- if (accept1(parser, PM_TOKEN_NEWLINE) && match1(parser, PM_TOKEN_BRACKET_RIGHT)) {
18000
+ if (accepted_newline && match1(parser, PM_TOKEN_BRACKET_RIGHT)) {
17957
18001
  break;
17958
18002
  }
17959
18003
 
17960
18004
  // Ensure that we have a comma between elements in the array.
17961
- if ((pm_array_node_size(array) != 0) && !accept1(parser, PM_TOKEN_COMMA)) {
17962
- const uint8_t *location = parser->previous.end;
17963
- PM_PARSER_ERR_FORMAT(parser, location, location, PM_ERR_ARRAY_SEPARATOR, pm_token_type_human(parser->current.type));
18005
+ if (array->elements.size > 0) {
18006
+ if (accept1(parser, PM_TOKEN_COMMA)) {
18007
+ // If there was a comma but we also accepts a newline,
18008
+ // then this is a syntax error.
18009
+ if (accepted_newline) {
18010
+ pm_parser_err_previous(parser, PM_ERR_INVALID_COMMA);
18011
+ }
18012
+ } else {
18013
+ // If there was no comma, then we need to add a syntax
18014
+ // error.
18015
+ const uint8_t *location = parser->previous.end;
18016
+ PM_PARSER_ERR_FORMAT(parser, location, location, PM_ERR_ARRAY_SEPARATOR, pm_token_type_human(parser->current.type));
17964
18017
 
17965
- parser->previous.start = location;
17966
- parser->previous.type = PM_TOKEN_MISSING;
18018
+ parser->previous.start = location;
18019
+ parser->previous.type = PM_TOKEN_MISSING;
18020
+ }
17967
18021
  }
17968
18022
 
17969
18023
  // If we have a right bracket immediately following a comma,
@@ -18119,14 +18173,32 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
18119
18173
  multi_target->base.location.start = lparen_loc.start;
18120
18174
  multi_target->base.location.end = rparen_loc.end;
18121
18175
 
18122
- if (match1(parser, PM_TOKEN_COMMA)) {
18123
- if (binding_power == PM_BINDING_POWER_STATEMENT) {
18124
- return parse_targets_validate(parser, (pm_node_t *) multi_target, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1));
18125
- }
18126
- return (pm_node_t *) multi_target;
18176
+ pm_node_t *result;
18177
+ if (match1(parser, PM_TOKEN_COMMA) && (binding_power == PM_BINDING_POWER_STATEMENT)) {
18178
+ result = parse_targets(parser, (pm_node_t *) multi_target, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1));
18179
+ accept1(parser, PM_TOKEN_NEWLINE);
18180
+ } else {
18181
+ result = (pm_node_t *) multi_target;
18127
18182
  }
18128
18183
 
18129
- return parse_target_validate(parser, (pm_node_t *) multi_target, false);
18184
+ if (context_p(parser, PM_CONTEXT_MULTI_TARGET)) {
18185
+ // All set, this is explicitly allowed by the parent
18186
+ // context.
18187
+ } else if (context_p(parser, PM_CONTEXT_FOR_INDEX) && match1(parser, PM_TOKEN_KEYWORD_IN)) {
18188
+ // All set, we're inside a for loop and we're parsing
18189
+ // multiple targets.
18190
+ } else if (binding_power != PM_BINDING_POWER_STATEMENT) {
18191
+ // Multi targets are not allowed when it's not a
18192
+ // statement level.
18193
+ pm_parser_err_node(parser, result, PM_ERR_WRITE_TARGET_UNEXPECTED);
18194
+ } else if (!match2(parser, PM_TOKEN_EQUAL, PM_TOKEN_PARENTHESIS_RIGHT)) {
18195
+ // Multi targets must be followed by an equal sign in
18196
+ // order to be valid (or a right parenthesis if they are
18197
+ // nested).
18198
+ pm_parser_err_node(parser, result, PM_ERR_WRITE_TARGET_UNEXPECTED);
18199
+ }
18200
+
18201
+ return result;
18130
18202
  }
18131
18203
 
18132
18204
  // If we have a single statement and are ending on a right parenthesis
@@ -18187,6 +18259,33 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
18187
18259
  pm_accepts_block_stack_pop(parser);
18188
18260
  expect1(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_EXPECT_RPAREN);
18189
18261
 
18262
+ // When we're parsing multi targets, we allow them to be followed by
18263
+ // a right parenthesis if they are at the statement level. This is
18264
+ // only possible if they are the final statement in a parentheses.
18265
+ // We need to explicitly reject that here.
18266
+ {
18267
+ pm_node_t *statement = statements->body.nodes[statements->body.size - 1];
18268
+
18269
+ if (PM_NODE_TYPE_P(statement, PM_SPLAT_NODE)) {
18270
+ pm_multi_target_node_t *multi_target = pm_multi_target_node_create(parser);
18271
+ pm_multi_target_node_targets_append(parser, multi_target, statement);
18272
+
18273
+ statement = (pm_node_t *) multi_target;
18274
+ statements->body.nodes[statements->body.size - 1] = statement;
18275
+ }
18276
+
18277
+ if (PM_NODE_TYPE_P(statement, PM_MULTI_TARGET_NODE)) {
18278
+ const uint8_t *offset = statement->location.end;
18279
+ pm_token_t operator = { .type = PM_TOKEN_EQUAL, .start = offset, .end = offset };
18280
+ pm_node_t *value = (pm_node_t *) pm_missing_node_create(parser, offset, offset);
18281
+
18282
+ statement = (pm_node_t *) pm_multi_write_node_create(parser, (pm_multi_target_node_t *) statement, &operator, value);
18283
+ statements->body.nodes[statements->body.size - 1] = statement;
18284
+
18285
+ pm_parser_err_node(parser, statement, PM_ERR_WRITE_TARGET_UNEXPECTED);
18286
+ }
18287
+ }
18288
+
18190
18289
  pop_block_exits(parser, previous_block_exits);
18191
18290
  pm_node_list_free(&current_block_exits);
18192
18291
 
@@ -18442,10 +18541,11 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
18442
18541
  case PM_TOKEN_HEREDOC_START: {
18443
18542
  // Here we have found a heredoc. We'll parse it and add it to the
18444
18543
  // list of strings.
18445
- pm_lex_mode_t *lex_mode = parser->lex_modes.current;
18446
- assert(lex_mode->mode == PM_LEX_HEREDOC);
18447
- pm_heredoc_quote_t quote = lex_mode->as.heredoc.quote;
18448
- pm_heredoc_indent_t indent = lex_mode->as.heredoc.indent;
18544
+ assert(parser->lex_modes.current->mode == PM_LEX_HEREDOC);
18545
+ pm_heredoc_lex_mode_t lex_mode = parser->lex_modes.current->as.heredoc.base;
18546
+
18547
+ size_t common_whitespace = (size_t) -1;
18548
+ parser->lex_modes.current->as.heredoc.common_whitespace = &common_whitespace;
18449
18549
 
18450
18550
  parser_lex(parser);
18451
18551
  pm_token_t opening = parser->previous;
@@ -18456,10 +18556,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
18456
18556
  if (match2(parser, PM_TOKEN_HEREDOC_END, PM_TOKEN_EOF)) {
18457
18557
  // If we get here, then we have an empty heredoc. We'll create
18458
18558
  // an empty content token and return an empty string node.
18459
- expect1_heredoc_term(parser, lex_mode);
18559
+ expect1_heredoc_term(parser, lex_mode.ident_start, lex_mode.ident_length);
18460
18560
  pm_token_t content = parse_strings_empty_content(parser->previous.start);
18461
18561
 
18462
- if (quote == PM_HEREDOC_QUOTE_BACKTICK) {
18562
+ if (lex_mode.quote == PM_HEREDOC_QUOTE_BACKTICK) {
18463
18563
  node = (pm_node_t *) pm_xstring_node_create_unescaped(parser, &opening, &content, &parser->previous, &PM_STRING_EMPTY);
18464
18564
  } else {
18465
18565
  node = (pm_node_t *) pm_string_node_create_unescaped(parser, &opening, &content, &parser->previous, &PM_STRING_EMPTY);
@@ -18486,18 +18586,17 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
18486
18586
  cast->closing_loc = PM_LOCATION_TOKEN_VALUE(&parser->current);
18487
18587
  cast->base.location = cast->opening_loc;
18488
18588
 
18489
- if (quote == PM_HEREDOC_QUOTE_BACKTICK) {
18589
+ if (lex_mode.quote == PM_HEREDOC_QUOTE_BACKTICK) {
18490
18590
  assert(sizeof(pm_string_node_t) == sizeof(pm_x_string_node_t));
18491
18591
  cast->base.type = PM_X_STRING_NODE;
18492
18592
  }
18493
18593
 
18494
- size_t common_whitespace = lex_mode->as.heredoc.common_whitespace;
18495
- if (indent == PM_HEREDOC_INDENT_TILDE && (common_whitespace != (size_t) -1) && (common_whitespace != 0)) {
18594
+ if (lex_mode.indent == PM_HEREDOC_INDENT_TILDE && (common_whitespace != (size_t) -1) && (common_whitespace != 0)) {
18496
18595
  parse_heredoc_dedent_string(&cast->unescaped, common_whitespace);
18497
18596
  }
18498
18597
 
18499
18598
  node = (pm_node_t *) cast;
18500
- expect1_heredoc_term(parser, lex_mode);
18599
+ expect1_heredoc_term(parser, lex_mode.ident_start, lex_mode.ident_length);
18501
18600
  } else {
18502
18601
  // If we get here, then we have multiple parts in the heredoc,
18503
18602
  // so we'll need to create an interpolated string node to hold
@@ -18511,15 +18610,13 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
18511
18610
  }
18512
18611
  }
18513
18612
 
18514
- size_t common_whitespace = lex_mode->as.heredoc.common_whitespace;
18515
-
18516
18613
  // Now that we have all of the parts, create the correct type of
18517
18614
  // interpolated node.
18518
- if (quote == PM_HEREDOC_QUOTE_BACKTICK) {
18615
+ if (lex_mode.quote == PM_HEREDOC_QUOTE_BACKTICK) {
18519
18616
  pm_interpolated_x_string_node_t *cast = pm_interpolated_xstring_node_create(parser, &opening, &opening);
18520
18617
  cast->parts = parts;
18521
18618
 
18522
- expect1_heredoc_term(parser, lex_mode);
18619
+ expect1_heredoc_term(parser, lex_mode.ident_start, lex_mode.ident_length);
18523
18620
  pm_interpolated_xstring_node_closing_set(cast, &parser->previous);
18524
18621
 
18525
18622
  cast->base.location = cast->opening_loc;
@@ -18528,7 +18625,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
18528
18625
  pm_interpolated_string_node_t *cast = pm_interpolated_string_node_create(parser, &opening, &parts, &opening);
18529
18626
  pm_node_list_free(&parts);
18530
18627
 
18531
- expect1_heredoc_term(parser, lex_mode);
18628
+ expect1_heredoc_term(parser, lex_mode.ident_start, lex_mode.ident_length);
18532
18629
  pm_interpolated_string_node_closing_set(cast, &parser->previous);
18533
18630
 
18534
18631
  cast->base.location = cast->opening_loc;
@@ -18537,9 +18634,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
18537
18634
 
18538
18635
  // If this is a heredoc that is indented with a ~, then we need
18539
18636
  // to dedent each line by the common leading whitespace.
18540
- if (indent == PM_HEREDOC_INDENT_TILDE && (common_whitespace != (size_t) -1) && (common_whitespace != 0)) {
18637
+ if (lex_mode.indent == PM_HEREDOC_INDENT_TILDE && (common_whitespace != (size_t) -1) && (common_whitespace != 0)) {
18541
18638
  pm_node_list_t *nodes;
18542
- if (quote == PM_HEREDOC_QUOTE_BACKTICK) {
18639
+ if (lex_mode.quote == PM_HEREDOC_QUOTE_BACKTICK) {
18543
18640
  nodes = &((pm_interpolated_x_string_node_t *) node)->parts;
18544
18641
  } else {
18545
18642
  nodes = &((pm_interpolated_string_node_t *) node)->parts;
@@ -21684,8 +21781,11 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc
21684
21781
  // Otherwise we'll look and see if the next token can be parsed as an infix
21685
21782
  // operator. If it can, then we'll parse it using parse_expression_infix.
21686
21783
  pm_binding_powers_t current_binding_powers;
21784
+ pm_token_type_t current_token_type;
21785
+
21687
21786
  while (
21688
- current_binding_powers = pm_binding_powers[parser->current.type],
21787
+ current_token_type = parser->current.type,
21788
+ current_binding_powers = pm_binding_powers[current_token_type],
21689
21789
  binding_power <= current_binding_powers.left &&
21690
21790
  current_binding_powers.binary
21691
21791
  ) {
@@ -21726,6 +21826,13 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc
21726
21826
  // If the operator is nonassoc and we should not be able to parse the
21727
21827
  // upcoming infix operator, break.
21728
21828
  if (current_binding_powers.nonassoc) {
21829
+ // If this is a non-assoc operator and we are about to parse the
21830
+ // exact same operator, then we need to add an error.
21831
+ if (match1(parser, current_token_type)) {
21832
+ PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_NON_ASSOCIATIVE_OPERATOR, pm_token_type_human(parser->current.type), pm_token_type_human(current_token_type));
21833
+ break;
21834
+ }
21835
+
21729
21836
  // If this is an endless range, then we need to reject a couple of
21730
21837
  // additional operators because it violates the normal operator
21731
21838
  // precedence rules. Those patterns are:
@@ -21735,7 +21842,7 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc
21735
21842
  //
21736
21843
  if (PM_NODE_TYPE_P(node, PM_RANGE_NODE) && ((pm_range_node_t *) node)->right == NULL) {
21737
21844
  if (match4(parser, PM_TOKEN_UAMPERSAND, PM_TOKEN_USTAR, PM_TOKEN_DOT, PM_TOKEN_AMPERSAND_DOT)) {
21738
- PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_NON_ASSOCIATIVE_OPERATOR, pm_token_type_human(parser->current.type), pm_token_type_human(parser->previous.type));
21845
+ PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_NON_ASSOCIATIVE_OPERATOR, pm_token_type_human(parser->current.type), pm_token_type_human(current_token_type));
21739
21846
  break;
21740
21847
  }
21741
21848
 
@@ -21857,6 +21964,7 @@ wrap_statements(pm_parser_t *parser, pm_statements_node_t *statements) {
21857
21964
  ));
21858
21965
 
21859
21966
  pm_arguments_node_arguments_append(arguments, (pm_node_t *) keywords);
21967
+ pm_node_flag_set((pm_node_t *) arguments, PM_ARGUMENTS_NODE_FLAGS_CONTAINS_KEYWORDS);
21860
21968
  }
21861
21969
 
21862
21970
  pm_statements_node_t *wrapped_statements = pm_statements_node_create(parser);