Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2032,7 +2032,8 @@ nodes:
comment: |
Represents reading a local variable. Note that this requires that a local
variable of the same name has already been written to in the same scope,
otherwise it is parsed as a method call.
otherwise it is parsed as a method call. Note that `it` default parameter
has `0it` as the name of this node.

foo
^^^
Expand Down
1 change: 1 addition & 0 deletions include/prism/diagnostic.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/diagnostic.c
Original file line number Diff line number Diff line change
Expand Up @@ -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`",
Expand Down
101 changes: 95 additions & 6 deletions src/prism.c
Original file line number Diff line number Diff line change
Expand Up @@ -4200,12 +4200,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);
}
Expand All @@ -4224,6 +4222,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.
*/
Expand All @@ -4249,6 +4256,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$/).
Expand Down Expand Up @@ -13549,8 +13607,13 @@ parse_pattern_primitive(pm_parser_t *parser, pm_diagnostic_id_t diag_id) {
parser_lex(parser);
pm_node_t *variable = (pm_node_t *) parse_variable(parser);
if (variable == NULL) {
PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->previous, PM_ERR_NO_LOCAL_VARIABLE, (int) (parser->previous.end - parser->previous.start), parser->previous.start);
variable = (pm_node_t *) pm_local_variable_read_node_create(parser, &parser->previous, 0);
if (parser->version != PM_OPTIONS_VERSION_CRUBY_3_3_0 && pm_token_is_it(parser->previous.start, parser->previous.end)) {
pm_constant_id_t name_id = pm_parser_constant_id_constant(parser, "0it", 3);
variable = (pm_node_t *) pm_local_variable_read_node_create_constant_id(parser, &parser->previous, name_id, 0);
} else {
PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->previous, PM_ERR_NO_LOCAL_VARIABLE, (int) (parser->previous.end - parser->previous.start), parser->previous.start);
variable = (pm_node_t *) pm_local_variable_read_node_create(parser, &parser->previous, 0);
}
}

return (pm_node_t *) pm_pinned_variable_node_create(parser, &operator, variable);
Expand Down Expand Up @@ -14452,6 +14515,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;
}
Expand Down Expand Up @@ -15059,6 +15147,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);
Expand Down
7 changes: 7 additions & 0 deletions test/prism/errors_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2069,6 +2069,13 @@ def test_forwarding_arg_and_block
], 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")
Expand Down
17 changes: 15 additions & 2 deletions test/prism/location_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ def test_CallNode

assert_location(CallNode, "foo bar baz")
assert_location(CallNode, "foo bar('baz')")

assert_location(CallNode, "-> { it }", 5...7, version: "3.3.0") do |node|
node.body.body.first
end
end

def test_CallAndWriteNode
Expand Down Expand Up @@ -570,6 +574,12 @@ def test_LocalVariableOrWriteNode

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
Expand Down Expand Up @@ -674,6 +684,9 @@ def test_PinnedExpressionNode

def test_PinnedVariableNode
assert_location(PinnedVariableNode, "bar = 1; foo in ^bar", 16...20, &:pattern)
assert_location(PinnedVariableNode, "proc { 1 in ^it }.call(1)", 12...15) do |node|
node.receiver.block.body.body.first.pattern
end
end

def test_PostExecutionNode
Expand Down Expand Up @@ -891,8 +904,8 @@ def test_all_tested

private

def assert_location(kind, source, expected = 0...source.length)
result = Prism.parse(source)
def assert_location(kind, source, expected = 0...source.length, **options)
result = Prism.parse(source, **options)
assert_equal [], result.comments
assert_equal [], result.errors

Expand Down