diff options
| author | Kevin Newton <kddnewton@gmail.com> | 2023-09-14 12:01:59 -0400 |
|---|---|---|
| committer | git <svn-admin@ruby-lang.org> | 2023-09-14 18:32:16 +0000 |
| commit | 63d1e056650293bb77f9a5c11600013afa801bba (patch) | |
| tree | 19f19f8069d4760df47a665be6fbbe0bf6f4c171 | |
| parent | fb1328e4676da4dfc174ccadc57899e2eac96a63 (diff) | |
[ruby/yarp] Handle errors when operator writes on a call with a block
https://github.com/ruby/yarp/commit/93bec2c173
| -rw-r--r-- | test/yarp/errors_test.rb | 24 | ||||
| -rw-r--r-- | yarp/diagnostic.c | 1 | ||||
| -rw-r--r-- | yarp/diagnostic.h | 1 | ||||
| -rw-r--r-- | yarp/yarp.c | 43 |
4 files changed, 56 insertions, 13 deletions
diff --git a/test/yarp/errors_test.rb b/test/yarp/errors_test.rb index 09abe3ee79..99ae840565 100644 --- a/test/yarp/errors_test.rb +++ b/test/yarp/errors_test.rb @@ -1163,6 +1163,30 @@ module YARP ] end + def test_call_with_block_and_write + source = "foo {} &&= 1" + assert_errors expression(source), source, [ + ["Unexpected write target", 0..6], + ["Unexpected operator after a call with a block", 7..10] + ] + end + + def test_call_with_block_or_write + source = "foo {} ||= 1" + assert_errors expression(source), source, [ + ["Unexpected write target", 0..6], + ["Unexpected operator after a call with a block", 7..10] + ] + end + + def test_call_with_block_operator_write + source = "foo {} += 1" + assert_errors expression(source), source, [ + ["Unexpected write target", 0..6], + ["Unexpected operator after a call with a block", 7..9] + ] + end + private def assert_errors(expected, source, errors) diff --git a/yarp/diagnostic.c b/yarp/diagnostic.c index e92a1ee491..7fd658d53d 100644 --- a/yarp/diagnostic.c +++ b/yarp/diagnostic.c @@ -184,6 +184,7 @@ static const char* const diagnostic_messages[YP_DIAGNOSTIC_ID_LEN] = { [YP_ERR_MULTI_ASSIGN_MULTI_SPLATS] = "Multiple splats in multiple assignment", [YP_ERR_NOT_EXPRESSION] = "Expected an expression after `not`", [YP_ERR_NUMBER_LITERAL_UNDERSCORE] = "Number literal ending with a `_`", + [YP_ERR_OPERATOR_WRITE_BLOCK] = "Unexpected operator after a call with a block", [YP_ERR_OPERATOR_MULTI_ASSIGN] = "Unexpected operator for a multiple assignment", [YP_ERR_PARAMETER_ASSOC_SPLAT_MULTI] = "Unexpected multiple `**` splat parameters", [YP_ERR_PARAMETER_BLOCK_MULTI] = "Multiple block parameters; only one block is allowed", diff --git a/yarp/diagnostic.h b/yarp/diagnostic.h index fd17945e0d..9a8975f061 100644 --- a/yarp/diagnostic.h +++ b/yarp/diagnostic.h @@ -151,6 +151,7 @@ typedef enum { YP_ERR_NOT_EXPRESSION, YP_ERR_NUMBER_LITERAL_UNDERSCORE, YP_ERR_OPERATOR_MULTI_ASSIGN, + YP_ERR_OPERATOR_WRITE_BLOCK, YP_ERR_PARAMETER_ASSOC_SPLAT_MULTI, YP_ERR_PARAMETER_BLOCK_MULTI, YP_ERR_PARAMETER_NAME_REPEAT, diff --git a/yarp/yarp.c b/yarp/yarp.c index 5f6eb0a48c..5a5afb7eb2 100644 --- a/yarp/yarp.c +++ b/yarp/yarp.c @@ -13216,6 +13216,20 @@ parse_assignment_value(yp_parser_t *parser, yp_binding_power_t previous_binding_ return value; } +// Ensures a call node that is about to become a call operator node does not +// have a block attached. If it does, then we'll need to add an error message +// and destroy the block. Ideally we would keep the node around so that +// consumers would still have access to it, but we don't have a great structure +// for that at the moment. +static void +parse_call_operator_write_block(yp_parser_t *parser, yp_call_node_t *call_node, const yp_token_t *operator) { + if (call_node->block != NULL) { + yp_diagnostic_list_append(&parser->error_list, operator->start, operator->end, YP_ERR_OPERATOR_WRITE_BLOCK); + yp_node_destroy(parser, (yp_node_t *) call_node->block); + call_node->block = NULL; + } +} + static inline yp_node_t * parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t previous_binding_power, yp_binding_power_t binding_power) { yp_token_t token = parser->current; @@ -13321,13 +13335,11 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t return result; } case YP_CALL_NODE: { - yp_call_node_t *call_node = (yp_call_node_t *) node; - // If we have a vcall (a method with no arguments and no // receiver that could have been a local variable) then we // will transform it into a local variable write. - if (yp_call_node_variable_call_p(call_node)) { - yp_location_t message_loc = call_node->message_loc; + if (yp_call_node_variable_call_p((yp_call_node_t *) node)) { + yp_location_t message_loc = ((yp_call_node_t *) node)->message_loc; yp_constant_id_t constant_id = yp_parser_local_add_location(parser, message_loc.start, message_loc.end); if (token_is_numbered_parameter(message_loc.start, message_loc.end)) { @@ -13345,6 +13357,9 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t parser_lex(parser); node = parse_target(parser, node); + assert(YP_NODE_TYPE_P(node, YP_CALL_NODE)); + parse_call_operator_write_block(parser, (yp_call_node_t *) node, &token); + yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ); return (yp_node_t *) yp_call_and_write_node_create(parser, (yp_call_node_t *) node, &token, value); } @@ -13422,13 +13437,11 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t return result; } case YP_CALL_NODE: { - yp_call_node_t *call_node = (yp_call_node_t *) node; - // If we have a vcall (a method with no arguments and no // receiver that could have been a local variable) then we // will transform it into a local variable write. - if (yp_call_node_variable_call_p(call_node)) { - yp_location_t message_loc = call_node->message_loc; + if (yp_call_node_variable_call_p((yp_call_node_t *) node)) { + yp_location_t message_loc = ((yp_call_node_t *) node)->message_loc; yp_constant_id_t constant_id = yp_parser_local_add_location(parser, message_loc.start, message_loc.end); if (token_is_numbered_parameter(message_loc.start, message_loc.end)) { @@ -13446,6 +13459,9 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t parser_lex(parser); node = parse_target(parser, node); + assert(YP_NODE_TYPE_P(node, YP_CALL_NODE)); + parse_call_operator_write_block(parser, (yp_call_node_t *) node, &token); + yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ); return (yp_node_t *) yp_call_or_write_node_create(parser, (yp_call_node_t *) node, &token, value); } @@ -13533,13 +13549,11 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t return result; } case YP_CALL_NODE: { - yp_call_node_t *call_node = (yp_call_node_t *) node; - // If we have a vcall (a method with no arguments and no // receiver that could have been a local variable) then we // will transform it into a local variable write. - if (yp_call_node_variable_call_p(call_node)) { - yp_location_t message_loc = call_node->message_loc; + if (yp_call_node_variable_call_p((yp_call_node_t *) node)) { + yp_location_t message_loc = ((yp_call_node_t *) node)->message_loc; yp_constant_id_t constant_id = yp_parser_local_add_location(parser, message_loc.start, message_loc.end); if (token_is_numbered_parameter(message_loc.start, message_loc.end)) { @@ -13554,8 +13568,11 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t return result; } - node = parse_target(parser, node); parser_lex(parser); + node = parse_target(parser, node); + + assert(YP_NODE_TYPE_P(node, YP_CALL_NODE)); + parse_call_operator_write_block(parser, (yp_call_node_t *) node, &token); yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR); return (yp_node_t *) yp_call_operator_write_node_create(parser, (yp_call_node_t *) node, &token, value); |
