[ruby/prism] Fix up multi target parsing

https://github.com/ruby/prism/commit/80cd335222
This commit is contained in:
Kevin Newton 2024-10-04 12:02:22 -04:00 committed by git
parent cd86393ad8
commit 75640037bf
4 changed files with 87 additions and 6 deletions

View File

@ -392,6 +392,9 @@ typedef enum {
/** a rescue statement within a module statement */
PM_CONTEXT_MODULE_RESCUE,
/** a multiple target expression */
PM_CONTEXT_MULTI_TARGET,
/** a parenthesized expression */
PM_CONTEXT_PARENS,

View File

@ -8570,6 +8570,7 @@ context_terminator(pm_context_t context, pm_token_t *token) {
case PM_CONTEXT_MAIN:
case PM_CONTEXT_DEF_PARAMS:
case PM_CONTEXT_DEFINED:
case PM_CONTEXT_MULTI_TARGET:
case PM_CONTEXT_TERNARY:
case PM_CONTEXT_RESCUE_MODIFIER:
return token->type == PM_TOKEN_EOF;
@ -8774,6 +8775,7 @@ context_human(pm_context_t context) {
case PM_CONTEXT_LOOP_PREDICATE: return "loop predicate";
case PM_CONTEXT_MAIN: return "top level context";
case PM_CONTEXT_MODULE: return "module definition";
case PM_CONTEXT_MULTI_TARGET: return "multiple targets";
case PM_CONTEXT_PARENS: return "parentheses";
case PM_CONTEXT_POSTEXE: return "'END' block";
case PM_CONTEXT_PREDICATE: return "predicate";
@ -13799,6 +13801,13 @@ parse_targets(pm_parser_t *parser, pm_node_t *first_target, pm_binding_power_t b
pm_node_t *splat = (pm_node_t *) pm_splat_node_create(parser, &star_operator, name);
pm_multi_target_node_targets_append(parser, result, splat);
has_rest = true;
} else if (match1(parser, PM_TOKEN_PARENTHESIS_LEFT)) {
context_push(parser, PM_CONTEXT_MULTI_TARGET);
pm_node_t *target = parse_expression(parser, binding_power, false, false, PM_ERR_EXPECT_EXPRESSION_AFTER_COMMA, (uint16_t) (depth + 1));
target = parse_target(parser, target, true, false);
pm_multi_target_node_targets_append(parser, result, target);
context_pop(parser);
} else if (token_begins_expression_p(parser->current.type)) {
pm_node_t *target = parse_expression(parser, binding_power, false, false, PM_ERR_EXPECT_EXPRESSION_AFTER_COMMA, (uint16_t) (depth + 1));
target = parse_target(parser, target, true, false);
@ -15501,6 +15510,7 @@ parse_return(pm_parser_t *parser, pm_node_t *node) {
case PM_CONTEXT_IF:
case PM_CONTEXT_LOOP_PREDICATE:
case PM_CONTEXT_MAIN:
case PM_CONTEXT_MULTI_TARGET:
case PM_CONTEXT_PARENS:
case PM_CONTEXT_POSTEXE:
case PM_CONTEXT_PREDICATE:
@ -15629,6 +15639,7 @@ parse_block_exit(pm_parser_t *parser, pm_node_t *node) {
case PM_CONTEXT_MODULE_ENSURE:
case PM_CONTEXT_MODULE_RESCUE:
case PM_CONTEXT_MODULE:
case PM_CONTEXT_MULTI_TARGET:
case PM_CONTEXT_PARENS:
case PM_CONTEXT_PREDICATE:
case PM_CONTEXT_RESCUE_MODIFIER:
@ -17780,6 +17791,7 @@ parse_retry(pm_parser_t *parser, const pm_node_t *node) {
case PM_CONTEXT_LAMBDA_BRACES:
case PM_CONTEXT_LAMBDA_DO_END:
case PM_CONTEXT_LOOP_PREDICATE:
case PM_CONTEXT_MULTI_TARGET:
case PM_CONTEXT_PARENS:
case PM_CONTEXT_POSTEXE:
case PM_CONTEXT_PREDICATE:
@ -17863,6 +17875,7 @@ parse_yield(pm_parser_t *parser, const pm_node_t *node) {
case PM_CONTEXT_LAMBDA_ENSURE:
case PM_CONTEXT_LAMBDA_RESCUE:
case PM_CONTEXT_LOOP_PREDICATE:
case PM_CONTEXT_MULTI_TARGET:
case PM_CONTEXT_PARENS:
case PM_CONTEXT_POSTEXE:
case PM_CONTEXT_PREDICATE:
@ -18120,14 +18133,32 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
multi_target->base.location.start = lparen_loc.start;
multi_target->base.location.end = rparen_loc.end;
if (match1(parser, PM_TOKEN_COMMA)) {
if (binding_power == PM_BINDING_POWER_STATEMENT) {
return parse_targets_validate(parser, (pm_node_t *) multi_target, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1));
}
return (pm_node_t *) multi_target;
pm_node_t *result;
if (match1(parser, PM_TOKEN_COMMA) && (binding_power == PM_BINDING_POWER_STATEMENT)) {
result = parse_targets(parser, (pm_node_t *) multi_target, PM_BINDING_POWER_INDEX, (uint16_t) (depth + 1));
accept1(parser, PM_TOKEN_NEWLINE);
} else {
result = (pm_node_t *) multi_target;
}
return parse_target_validate(parser, (pm_node_t *) multi_target, false);
if (context_p(parser, PM_CONTEXT_MULTI_TARGET)) {
// All set, this is explicitly allowed by the parent
// context.
} else if (context_p(parser, PM_CONTEXT_FOR_INDEX) && match1(parser, PM_TOKEN_KEYWORD_IN)) {
// All set, we're inside a for loop and we're parsing
// multiple targets.
} else if (binding_power != PM_BINDING_POWER_STATEMENT) {
// Multi targets are not allowed when it's not a
// statement level.
pm_parser_err_node(parser, result, PM_ERR_WRITE_TARGET_UNEXPECTED);
} else if (!match2(parser, PM_TOKEN_EQUAL, PM_TOKEN_PARENTHESIS_RIGHT)) {
// Multi targets must be followed by an equal sign in
// order to be valid (or a right parenthesis if they are
// nested).
pm_parser_err_node(parser, result, PM_ERR_WRITE_TARGET_UNEXPECTED);
}
return result;
}
// If we have a single statement and are ending on a right parenthesis
@ -18184,6 +18215,17 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
}
}
// When we're parsing multi targets, we allow them to be followed by
// a right parenthesis if they are at the statement level. This is
// only possible if they are the final statement in a parentheses.
// We need to explicitly reject that here.
{
const pm_node_t *statement = statements->body.nodes[statements->body.size - 1];
if (PM_NODE_TYPE_P(statement, PM_MULTI_TARGET_NODE)) {
pm_parser_err_node(parser, statement, PM_ERR_WRITE_TARGET_UNEXPECTED);
}
}
context_pop(parser);
pm_accepts_block_stack_pop(parser);
expect1(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_EXPECT_RPAREN);

View File

@ -0,0 +1,19 @@
( + ( * ) )
^~~~~ unexpected write target
( a ( * ) )
^~~~~ unexpected write target
( 1 + ( * ) )
^~~~~ unexpected write target
( .. ( * ) )
^~~~~ unexpected write target
( a = ( * ) )
^~~~~ unexpected write target
( * = ( * ) )
^~~~~ unexpected write target
( a if ( * ) )
^~~~~ unexpected write target
( 1; ( * ) )
^~~~~ unexpected write target
( def f() = ( * ) )
^~~~~ unexpected write target

View File

@ -0,0 +1,17 @@
[(*),]
^~~ unexpected write target
[1,(a,*b,(c,d)),1]
^~~~~~~~~~~~ unexpected write target
{a:(*),}
^~~ unexpected write target
[1+(*),]
^~~ unexpected write target
x=(*),1
^~~ unexpected write target
p((*),)
^~~ unexpected write target
p (*),1
^~~ unexpected write target
x = def f = (*),1
^~~ unexpected write target