From 8587ce292b62ce5dc21904981dc536e7e4e56d61 Mon Sep 17 00:00:00 2001 From: Hiroya Fujinami Date: Sat, 5 Sep 2026 18:17:56 +0900 Subject: [PATCH] [ruby/prism] Avoid quadratic value-expression check on and/or chains (https://github.com/ruby/prism/pull/4221) Ref https://bugs.ruby-lang.org/issues/22294 pm_check_value_expression descended the left operand of every and/or node it visited. Because pm_and_node_create and pm_or_node_create already assert the value of their left operand when the node is built, walking a left-associative chain such as `a && a && ... && a` re-checked the whole left spine once per operator, which is quadratic in the length of the chain: 16k operators took seconds. The left operand of an existing and/or node was therefore already checked, so stop at the node instead of descending. This makes the check linear. The only observable change is that a void value on the left spine of a chain is now reported once, at the innermost node where it is created, rather than once per enclosing operator; the errors fixture that pinned the duplicate is updated to match. https://github.com/ruby/prism/commit/b19a0a5bc0 --- prism/prism.c | 18 ++++++++---------- .../destroy_call_operator_write_arguments.txt | 1 - 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 8754aecc9bdd47..2c84490dc57de7 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -1359,16 +1359,14 @@ pm_check_value_expression(pm_parser_t *parser, pm_node_t *node) { node = UP(cast->statements); break; } - case PM_AND_NODE: { - pm_and_node_t *cast = (pm_and_node_t *) node; - node = cast->left; - break; - } - case PM_OR_NODE: { - pm_or_node_t *cast = (pm_or_node_t *) node; - node = cast->left; - break; - } + case PM_AND_NODE: + case PM_OR_NODE: + // The left operand of an and/or node was already checked for a + // value when the node was created, so descending into it again + // would re-report the same void value and, in a chain such as + // `a && a && ...`, walk the whole left branch on every operator, + // which is quadratic in the length of the chain. + return NULL; case PM_LOCAL_VARIABLE_WRITE_NODE: { pm_local_variable_write_node_t *cast = (pm_local_variable_write_node_t *) node; diff --git a/test/prism/errors/destroy_call_operator_write_arguments.txt b/test/prism/errors/destroy_call_operator_write_arguments.txt index b6933d61d13132..b0768b1f1ad1e5 100644 --- a/test/prism/errors/destroy_call_operator_write_arguments.txt +++ b/test/prism/errors/destroy_call_operator_write_arguments.txt @@ -1,6 +1,5 @@ t next&&do end&= ^~ unexpected 'do'; expected an expression after the operator - ^~~~ unexpected void value expression ^~~~ unexpected void value expression ^~ unexpected '&=', expecting end-of-input ^~ unexpected '&=', ignoring it