Repository: ruby/prism
Pull request #2830 (2024-05-21 15:03:12)

Fix up ruby_parser interpolation concatenation
Fixes https://github.com/ruby/prism/issues/2828


Patch:
diff --git a/config.yml b/config.yml
index cfcfd2e7..6b4b1aa9 100644
--- a/config.yml
+++ b/config.yml
@@ -114,7 +114,6 @@ errors:
   - EXPRESSION_NOT_WRITABLE_FILE
   - EXPRESSION_NOT_WRITABLE_LINE
   - EXPRESSION_NOT_WRITABLE_NIL
-  - EXPRESSION_NOT_WRITABLE_NUMBERED
   - EXPRESSION_NOT_WRITABLE_SELF
   - EXPRESSION_NOT_WRITABLE_TRUE
   - FLOAT_PARSE
@@ -186,10 +185,9 @@ errors:
   - NO_LOCAL_VARIABLE
   - NOT_EXPRESSION
   - NUMBER_LITERAL_UNDERSCORE
-  - NUMBERED_PARAMETER_INNER_BLOCK
   - NUMBERED_PARAMETER_IT
   - NUMBERED_PARAMETER_ORDINARY
-  - NUMBERED_PARAMETER_OUTER_BLOCK
+  - NUMBERED_PARAMETER_OUTER_SCOPE
   - OPERATOR_MULTI_ASSIGN
   - OPERATOR_WRITE_ARGUMENTS
   - OPERATOR_WRITE_BLOCK
diff --git a/include/prism/parser.h b/include/prism/parser.h
index ff126184..8054e332 100644
--- a/include/prism/parser.h
+++ b/include/prism/parser.h
@@ -609,7 +609,6 @@ static const uint8_t PM_SCOPE_PARAMETERS_FORWARDING_KEYWORDS = 0x10;
 static const uint8_t PM_SCOPE_PARAMETERS_FORWARDING_BLOCK = 0x20;
 static const uint8_t PM_SCOPE_PARAMETERS_FORWARDING_ALL = 0x40;
 
-static const int8_t PM_SCOPE_NUMBERED_PARAMETERS_INNER = -2;
 static const int8_t PM_SCOPE_NUMBERED_PARAMETERS_DISALLOWED = -1;
 static const int8_t PM_SCOPE_NUMBERED_PARAMETERS_NONE = 0;
 
diff --git a/lib/prism/translation/ruby_parser.rb b/lib/prism/translation/ruby_parser.rb
index 43aac23b..6e745a90 100644
--- a/lib/prism/translation/ruby_parser.rb
+++ b/lib/prism/translation/ruby_parser.rb
@@ -905,8 +905,13 @@ module Prism
                 results << result
                 state = :interpolated_content
               end
-            else
-              results << result
+            when :interpolated_content
+              if result.is_a?(Array) && result[0] == :str && results[-1][0] == :str && (results[-1].line_max == result.line)
+                results[-1][1] << result[1]
+                results[-1].line_max = result.line_max
+              else
+                results << result
+              end
             end
           end
         end
diff --git a/src/prism.c b/src/prism.c
index e1467993..a382d070 100644
--- a/src/prism.c
+++ b/src/prism.c
@@ -13365,20 +13365,14 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod
             return (pm_node_t *) node;
         }
         case PM_LOCAL_VARIABLE_READ_NODE: {
+            pm_refute_numbered_parameter(parser, target->location.start, target->location.end);
             pm_local_variable_read_node_t *local_read = (pm_local_variable_read_node_t *) target;
 
             pm_constant_id_t name = local_read->name;
-            pm_location_t name_loc = target->location;
-
             uint32_t depth = local_read->depth;
-            pm_scope_t *scope = pm_parser_scope_find(parser, depth);
-
-            if (pm_token_is_numbered_parameter(target->location.start, target->location.end)) {
-                pm_diagnostic_id_t diag_id = scope->parameters > PM_SCOPE_NUMBERED_PARAMETERS_NONE ? PM_ERR_EXPRESSION_NOT_WRITABLE_NUMBERED : PM_ERR_PARAMETER_NUMBERED_RESERVED;
-                PM_PARSER_ERR_FORMAT(parser, target->location.start, target->location.end, diag_id, target->location.start);
-            }
+            pm_locals_unread(&pm_parser_scope_find(parser, depth)->locals, name);
 
-            pm_locals_unread(&scope->locals, name);
+            pm_location_t name_loc = target->location;
             pm_node_destroy(parser, target);
 
             return (pm_node_t *) pm_local_variable_write_node_create(parser, name, depth, value, &name_loc, operator);
@@ -15456,13 +15450,6 @@ parse_string_part(pm_parser_t *parser) {
             expect1(parser, PM_TOKEN_EMBEXPR_END, PM_ERR_EMBEXPR_END);
             pm_token_t closing = parser->previous;
 
-            // If this set of embedded statements only contains a single
-            // statement, then Ruby does not consider it as a possible statement
-            // that could emit a line event.
-            if (statements != NULL && statements->body.size == 1) {
-                pm_node_flag_unset(statements->body.nodes[0], PM_NODE_FLAG_NEWLINE);
-            }
-
             return (pm_node_t *) pm_embedded_statements_node_create(parser, &opening, statements, &closing);
         }
 
@@ -15835,34 +15822,17 @@ parse_variable(pm_parser_t *parser) {
         } else if (current_scope->parameters & PM_SCOPE_PARAMETERS_IT) {
             pm_parser_err_previous(parser, PM_ERR_NUMBERED_PARAMETER_IT);
         } else if (outer_scope_using_numbered_parameters_p(parser)) {
-            pm_parser_err_previous(parser, PM_ERR_NUMBERED_PARAMETER_OUTER_BLOCK);
-        } else if (current_scope->numbered_parameters == PM_SCOPE_NUMBERED_PARAMETERS_INNER) {
-            pm_parser_err_previous(parser, PM_ERR_NUMBERED_PARAMETER_INNER_BLOCK);
+            pm_parser_err_previous(parser, PM_ERR_NUMBERED_PARAMETER_OUTER_SCOPE);
         } else {
             // Indicate that this scope is using numbered params so that child
             // scopes cannot. We subtract the value for the character '0' to get
             // the actual integer value of the number (only _1 through _9 are
             // valid).
             int8_t numbered_parameters = (int8_t) (parser->previous.start[1] - '0');
+            current_scope->parameters |= PM_SCOPE_PARAMETERS_NUMBERED;
 
-            // If we're about to match an =, then this is an invalid use of
-            // numbered parameters. We'll create all of the necessary
-            // infrastructure around it, but not actually mark the scope as
-            // using numbered parameters so that we can get the right error
-            // message.
-            if (!match1(parser, PM_TOKEN_EQUAL)) {
-                current_scope->parameters |= PM_SCOPE_PARAMETERS_NUMBERED;
-
-                if (numbered_parameters > current_scope->numbered_parameters) {
-                    current_scope->numbered_parameters = numbered_parameters;
-                }
-
-                // Go through the parent scopes and mark them as being
-                // disallowed from using numbered parameters because this inner
-                // scope is using them.
-                for (pm_scope_t *scope = current_scope->previous; scope != NULL && !scope->closed; scope = scope->previous) {
-                    scope->numbered_parameters = PM_SCOPE_NUMBERED_PARAMETERS_INNER;
-                }
+            if (numbered_parameters > current_scope->numbered_parameters) {
+                current_scope->numbered_parameters = numbered_parameters;
             }
 
             // When you use a numbered parameter, it implies the existence
diff --git a/templates/src/diagnostic.c.erb b/templates/src/diagnostic.c.erb
index 30d6b0ff..57c52602 100644
--- a/templates/src/diagnostic.c.erb
+++ b/templates/src/diagnostic.c.erb
@@ -197,7 +197,6 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = {
     [PM_ERR_EXPRESSION_NOT_WRITABLE_FILE]       = { "Can't assign to __FILE__", PM_ERROR_LEVEL_SYNTAX },
     [PM_ERR_EXPRESSION_NOT_WRITABLE_LINE]       = { "Can't assign to __LINE__", PM_ERROR_LEVEL_SYNTAX },
     [PM_ERR_EXPRESSION_NOT_WRITABLE_NIL]        = { "Can't assign to nil", PM_ERROR_LEVEL_SYNTAX },
-    [PM_ERR_EXPRESSION_NOT_WRITABLE_NUMBERED]   = { "Can't assign to numbered parameter %.2s", PM_ERROR_LEVEL_SYNTAX },
     [PM_ERR_EXPRESSION_NOT_WRITABLE_SELF]       = { "Can't change the value of self", PM_ERROR_LEVEL_SYNTAX },
     [PM_ERR_EXPRESSION_NOT_WRITABLE_TRUE]       = { "Can't assign to true", PM_ERROR_LEVEL_SYNTAX },
     [PM_ERR_FLOAT_PARSE]                        = { "could not parse the float '%.*s'", PM_ERROR_LEVEL_SYNTAX },
@@ -268,10 +267,9 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = {
     [PM_ERR_NOT_EXPRESSION]                     = { "expected an expression after `not`", PM_ERROR_LEVEL_SYNTAX },
     [PM_ERR_NO_LOCAL_VARIABLE]                  = { "%.*s: no such local variable", PM_ERROR_LEVEL_SYNTAX },
     [PM_ERR_NUMBER_LITERAL_UNDERSCORE]          = { "number literal ending with a `_`", PM_ERROR_LEVEL_SYNTAX },
-    [PM_ERR_NUMBERED_PARAMETER_INNER_BLOCK]     = { "numbered parameter is already used in inner block", PM_ERROR_LEVEL_SYNTAX },
     [PM_ERR_NUMBERED_PARAMETER_IT]              = { "numbered parameters are not allowed when an 'it' parameter is defined", PM_ERROR_LEVEL_SYNTAX },
     [PM_ERR_NUMBERED_PARAMETER_ORDINARY]        = { "numbered parameters are not allowed when an ordinary parameter is defined", PM_ERROR_LEVEL_SYNTAX },
-    [PM_ERR_NUMBERED_PARAMETER_OUTER_BLOCK]     = { "numbered parameter is already used in outer block", PM_ERROR_LEVEL_SYNTAX },
+    [PM_ERR_NUMBERED_PARAMETER_OUTER_SCOPE]     = { "numbered parameter is already used in outer scope", PM_ERROR_LEVEL_SYNTAX },
     [PM_ERR_OPERATOR_MULTI_ASSIGN]              = { "unexpected operator for a multiple assignment", PM_ERROR_LEVEL_SYNTAX },
     [PM_ERR_OPERATOR_WRITE_ARGUMENTS]           = { "unexpected operator after a call with arguments", PM_ERROR_LEVEL_SYNTAX },
     [PM_ERR_OPERATOR_WRITE_BLOCK]               = { "unexpected operator after a call with a block", PM_ERROR_LEVEL_SYNTAX },


Test patch:
diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb
index 6f926452..3670b90d 100644
--- a/test/prism/errors_test.rb
+++ b/test/prism/errors_test.rb
@@ -1377,7 +1377,7 @@ module Prism
 
     def test_double_scope_numbered_parameters
       source = "-> { _1 + -> { _2 } }"
-      errors = [["numbered parameter is already used in outer block", 15..17]]
+      errors = [["numbered parameter is already used in outer scope", 15..17]]
 
       assert_errors expression(source), source, errors
     end
diff --git a/test/prism/ruby_parser_test.rb b/test/prism/ruby_parser_test.rb
index 8edeac4b..b4afff07 100644
--- a/test/prism/ruby_parser_test.rb
+++ b/test/prism/ruby_parser_test.rb
@@ -28,19 +28,15 @@ module Prism
     base = File.join(__dir__, "fixtures")
 
     todos = %w[
-      heredocs_nested.txt
       newline_terminated.txt
       regex_char_width.txt
       seattlerb/bug169.txt
-      seattlerb/dstr_evstr.txt
-      seattlerb/heredoc_squiggly_interp.txt
       seattlerb/masgn_colon3.txt
       seattlerb/messy_op_asgn_lineno.txt
       seattlerb/op_asgn_primary_colon_const_command_call.txt
       seattlerb/parse_line_evstr_after_break.txt
       seattlerb/regexp_esc_C_slash.txt
       seattlerb/str_lit_concat_bad_encodings.txt
-      seattlerb/str_pct_nested_nested.txt
       unescaping.txt
       unparser/corpus/literal/kwbegin.txt
       unparser/corpus/literal/send.txt
@@ -76,12 +72,10 @@ module Prism
       tilde_heredocs.txt
       unparser/corpus/literal/literal.txt
       while.txt
-      whitequark/class_definition_in_while_cond.txt
       whitequark/cond_eflipflop.txt
       whitequark/cond_iflipflop.txt
       whitequark/cond_match_current_line.txt
       whitequark/dedenting_heredoc.txt
-      whitequark/if_while_after_class__since_32.txt
       whitequark/lvar_injecting_match.txt
       whitequark/not.txt
       whitequark/numparam_ruby_bug_19025.txt