diff --git a/config.yml b/config.yml index 6b4b1aa9c0e..cfcfd2e7aad 100644 --- a/config.yml +++ b/config.yml @@ -114,6 +114,7 @@ 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 @@ -185,9 +186,10 @@ errors: - NO_LOCAL_VARIABLE - NOT_EXPRESSION - NUMBER_LITERAL_UNDERSCORE + - NUMBERED_PARAMETER_INNER_BLOCK - NUMBERED_PARAMETER_IT - NUMBERED_PARAMETER_ORDINARY - - NUMBERED_PARAMETER_OUTER_SCOPE + - NUMBERED_PARAMETER_OUTER_BLOCK - OPERATOR_MULTI_ASSIGN - OPERATOR_WRITE_ARGUMENTS - OPERATOR_WRITE_BLOCK diff --git a/include/prism/parser.h b/include/prism/parser.h index 8054e332f73..ff1261841cc 100644 --- a/include/prism/parser.h +++ b/include/prism/parser.h @@ -609,6 +609,7 @@ 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/src/prism.c b/src/prism.c index a382d070e57..4b7397ef1ef 100644 --- a/src/prism.c +++ b/src/prism.c @@ -13365,14 +13365,20 @@ 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_locals_unread(&pm_parser_scope_find(parser, depth)->locals, name); + pm_scope_t *scope = pm_parser_scope_find(parser, depth); - pm_location_t name_loc = target->location; + 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(&scope->locals, name); pm_node_destroy(parser, target); return (pm_node_t *) pm_local_variable_write_node_create(parser, name, depth, value, &name_loc, operator); @@ -15450,6 +15456,13 @@ 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); } @@ -15822,17 +15835,34 @@ 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_SCOPE); + 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); } 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 (numbered_parameters > current_scope->numbered_parameters) { - current_scope->numbered_parameters = numbered_parameters; + // 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; + } } // 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 57c52602f42..30d6b0ffedc 100644 --- a/templates/src/diagnostic.c.erb +++ b/templates/src/diagnostic.c.erb @@ -197,6 +197,7 @@ 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 }, @@ -267,9 +268,10 @@ 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_SCOPE] = { "numbered parameter is already used in outer scope", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_NUMBERED_PARAMETER_OUTER_BLOCK] = { "numbered parameter is already used in outer block", 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 }, diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb index 3670b90dd76..6f926452fa8 100644 --- a/test/prism/errors_test.rb +++ b/test/prism/errors_test.rb @@ -1377,7 +1377,7 @@ def test_defining_numbered_parameter def test_double_scope_numbered_parameters source = "-> { _1 + -> { _2 } }" - errors = [["numbered parameter is already used in outer scope", 15..17]] + errors = [["numbered parameter is already used in outer block", 15..17]] assert_errors expression(source), source, errors end