From 603f2ca730cc62818a7a9852291f5877cbadd55d Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 12 Jan 2024 09:12:43 -0800 Subject: [ruby/prism] Parse `it` default parameter https://github.com/ruby/prism/commit/a0c5361b9f --- prism/diagnostic.c | 1 + prism/diagnostic.h | 1 + prism/prism.c | 92 +++++++++++++++++++++++++++++++++++++++++++-- test/prism/errors_test.rb | 7 ++++ test/prism/location_test.rb | 6 +++ 5 files changed, 103 insertions(+), 4 deletions(-) diff --git a/prism/diagnostic.c b/prism/diagnostic.c index c779955eb3..ed47a1cdad 100644 --- a/prism/diagnostic.c +++ b/prism/diagnostic.c @@ -175,6 +175,7 @@ static const char* const diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = { [PM_ERR_INVALID_PERCENT] = "invalid `%` token", // TODO WHAT? [PM_ERR_INVALID_TOKEN] = "invalid token", // TODO WHAT? [PM_ERR_INVALID_VARIABLE_GLOBAL] = "invalid global variable", + [PM_ERR_IT_NOT_ALLOWED] = "`it` is not allowed when an ordinary parameter is defined", [PM_ERR_LAMBDA_OPEN] = "expected a `do` keyword or a `{` to open the lambda block", [PM_ERR_LAMBDA_TERM_BRACE] = "expected a lambda block beginning with `{` to end with `}`", [PM_ERR_LAMBDA_TERM_END] = "expected a lambda block beginning with `do` to end with `end`", diff --git a/prism/diagnostic.h b/prism/diagnostic.h index da430b5438..fb41102361 100644 --- a/prism/diagnostic.h +++ b/prism/diagnostic.h @@ -167,6 +167,7 @@ typedef enum { PM_ERR_INVALID_PERCENT, PM_ERR_INVALID_TOKEN, PM_ERR_INVALID_VARIABLE_GLOBAL, + PM_ERR_IT_NOT_ALLOWED, PM_ERR_LAMBDA_OPEN, PM_ERR_LAMBDA_TERM_BRACE, PM_ERR_LAMBDA_TERM_END, diff --git a/prism/prism.c b/prism/prism.c index 83b641abc7..0778667f96 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -4196,12 +4196,10 @@ pm_local_variable_or_write_node_create(pm_parser_t *parser, pm_node_t *target, c } /** - * Allocate a new LocalVariableReadNode node. + * Allocate a new LocalVariableReadNode node with constant_id. */ static pm_local_variable_read_node_t * -pm_local_variable_read_node_create(pm_parser_t *parser, const pm_token_t *name, uint32_t depth) { - pm_constant_id_t name_id = pm_parser_constant_id_token(parser, name); - +pm_local_variable_read_node_create_constant_id(pm_parser_t *parser, const pm_token_t *name, pm_constant_id_t name_id, uint32_t depth) { if (parser->current_param_name == name_id) { pm_parser_err_token(parser, name, PM_ERR_PARAMETER_CIRCULAR); } @@ -4220,6 +4218,15 @@ pm_local_variable_read_node_create(pm_parser_t *parser, const pm_token_t *name, return node; } +/** + * Allocate a new LocalVariableReadNode node. + */ +static pm_local_variable_read_node_t * +pm_local_variable_read_node_create(pm_parser_t *parser, const pm_token_t *name, uint32_t depth) { + pm_constant_id_t name_id = pm_parser_constant_id_token(parser, name); + return pm_local_variable_read_node_create_constant_id(parser, name, name_id, depth); +} + /** * Allocate and initialize a new LocalVariableWriteNode node. */ @@ -4245,6 +4252,57 @@ pm_local_variable_write_node_create(pm_parser_t *parser, pm_constant_id_t name, return node; } +/** + * Returns true if the given bounds comprise `it`. + */ +static inline bool +pm_token_is_it(const uint8_t *start, const uint8_t *end) { + return (end - start == 2) && (start[0] == 'i') && (start[1] == 't'); +} + +/** + * Returns true if the given node is `it` default parameter. + */ +static inline bool +pm_node_is_it(pm_parser_t *parser, pm_node_t *node) { + // Check if it's a local variable reference + if (node->type != PM_CALL_NODE) { + return false; + } + + // Check if it's a variable call + pm_call_node_t *call_node = (pm_call_node_t *) node; + if (!pm_call_node_variable_call_p(call_node)) { + return false; + } + + // Check if it's called `it` + pm_constant_id_t id = ((pm_call_node_t *)node)->name; + pm_constant_t *constant = pm_constant_pool_id_to_constant(&parser->constant_pool, id); + return pm_token_is_it(constant->start, constant->start + constant->length); +} + +/** + * Convert a `it` variable call node to a node for `it` default parameter. + */ +static pm_node_t * +pm_node_check_it(pm_parser_t *parser, pm_node_t *node) { + if ( + (parser->version != PM_OPTIONS_VERSION_CRUBY_3_3_0) && + !parser->current_scope->closed && + pm_node_is_it(parser, node) + ) { + if (parser->current_scope->explicit_params) { + pm_parser_err_previous(parser, PM_ERR_IT_NOT_ALLOWED); + } else { + pm_node_destroy(parser, node); + pm_constant_id_t name_id = pm_parser_constant_id_constant(parser, "0it", 3); + node = (pm_node_t *) pm_local_variable_read_node_create_constant_id(parser, &parser->previous, name_id, 0); + } + } + return node; +} + /** * Returns true if the given bounds comprise a numbered parameter (i.e., they * are of the form /^_\d$/). @@ -14449,6 +14507,31 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if ((binding_power == PM_BINDING_POWER_STATEMENT) && match1(parser, PM_TOKEN_COMMA)) { node = parse_targets_validate(parser, node, PM_BINDING_POWER_INDEX); } + else { + // Check if `it` is not going to be assigned. + switch (parser->current.type) { + case PM_TOKEN_AMPERSAND_AMPERSAND_EQUAL: + case PM_TOKEN_AMPERSAND_EQUAL: + case PM_TOKEN_CARET_EQUAL: + case PM_TOKEN_EQUAL: + case PM_TOKEN_GREATER_GREATER_EQUAL: + case PM_TOKEN_LESS_LESS_EQUAL: + case PM_TOKEN_MINUS_EQUAL: + case PM_TOKEN_PARENTHESIS_RIGHT: + case PM_TOKEN_PERCENT_EQUAL: + case PM_TOKEN_PIPE_EQUAL: + case PM_TOKEN_PIPE_PIPE_EQUAL: + case PM_TOKEN_PLUS_EQUAL: + case PM_TOKEN_SLASH_EQUAL: + case PM_TOKEN_STAR_EQUAL: + case PM_TOKEN_STAR_STAR_EQUAL: + break; + default: + // Once we know it's neither a method call nor an assignment, + // we can finally create `it` default parameter. + node = pm_node_check_it(parser, node); + } + } return node; } @@ -15056,6 +15139,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if (match2(parser, PM_TOKEN_DOT, PM_TOKEN_COLON_COLON)) { receiver = parse_variable_call(parser); + receiver = pm_node_check_it(parser, receiver); saved_param_name = pm_parser_current_param_name_unset(parser); pm_parser_scope_push(parser, true); diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb index d1af9a5dae..60322585f0 100644 --- a/test/prism/errors_test.rb +++ b/test/prism/errors_test.rb @@ -2069,6 +2069,13 @@ module Prism ], compare_ripper: false # Ripper does not check 'both block arg and actual block given'. end + def test_it_with_ordinary_parameter + source = "proc { || it }" + errors = [["`it` is not allowed when an ordinary parameter is defined", 10..12]] + + assert_errors expression(source), source, errors, compare_ripper: false + end + private def assert_errors(expected, source, errors, compare_ripper: RUBY_ENGINE == "ruby") diff --git a/test/prism/location_test.rb b/test/prism/location_test.rb index e5b7546925..a36f5fe011 100644 --- a/test/prism/location_test.rb +++ b/test/prism/location_test.rb @@ -570,6 +570,12 @@ module Prism def test_LocalVariableReadNode assert_location(LocalVariableReadNode, "foo = 1; foo", 9...12) + assert_location(LocalVariableReadNode, "-> { it }", 5...7) do |node| + node.body.body.first + end + assert_location(LocalVariableReadNode, "foo { it }", 6...8) do |node| + node.block.body.body.first + end end def test_LocalVariableTargetNode -- cgit v1.2.3