summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Newton <kddnewton@gmail.com>2023-09-14 12:01:59 -0400
committergit <svn-admin@ruby-lang.org>2023-09-14 18:32:16 +0000
commit63d1e056650293bb77f9a5c11600013afa801bba (patch)
tree19f19f8069d4760df47a665be6fbbe0bf6f4c171
parentfb1328e4676da4dfc174ccadc57899e2eac96a63 (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.rb24
-rw-r--r--yarp/diagnostic.c1
-rw-r--r--yarp/diagnostic.h1
-rw-r--r--yarp/yarp.c43
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);