summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakashi Kokubun <takashikkbn@gmail.com>2024-01-12 09:12:43 -0800
committergit <svn-admin@ruby-lang.org>2024-01-17 17:47:33 +0000
commit603f2ca730cc62818a7a9852291f5877cbadd55d (patch)
tree2892e2da616f40b51cb3e6fe97341562cfb45762
parentcd4290910c7f3f63118567101bf6cec4df90361a (diff)
[ruby/prism] Parse `it` default parameter
https://github.com/ruby/prism/commit/a0c5361b9f
-rw-r--r--prism/diagnostic.c1
-rw-r--r--prism/diagnostic.h1
-rw-r--r--prism/prism.c92
-rw-r--r--test/prism/errors_test.rb7
-rw-r--r--test/prism/location_test.rb6
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);
}
@@ -4221,6 +4219,15 @@ pm_local_variable_read_node_create(pm_parser_t *parser, const pm_token_t *name,
}
/**
+ * 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.
*/
static pm_local_variable_write_node_t *
@@ -4246,6 +4253,57 @@ pm_local_variable_write_node_create(pm_parser_t *parser, pm_constant_id_t name,
}
/**
+ * 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