Avoid allocation for positional splat for literal array keyword argument

If all nodes in the array are safe, then it is safe to avoid
allocation for the positional splat:

```ruby
m(*a, kw: [:a])   # Safe
m(*a, kw: [meth]) # Unsafe
```

This avoids an unnecessary allocation in a Rails method call.
Details: https://github.com/rails/rails/pull/54949/files#r2052645431
This commit is contained in:
Jeremy Evans 2025-06-19 17:57:20 -07:00
parent d84a811f31
commit 353fa6f0ba
3 changed files with 31 additions and 0 deletions

View File

@ -6643,6 +6643,14 @@ setup_args_dup_rest_p(const NODE *argn)
return false;
case NODE_COLON2:
return setup_args_dup_rest_p(RNODE_COLON2(argn)->nd_head);
case NODE_LIST:
while (argn) {
if (setup_args_dup_rest_p(RNODE_LIST(argn)->nd_head)) {
return true;
}
argn = RNODE_LIST(argn)->nd_next;
}
return false;
default:
return true;
}

View File

@ -1882,6 +1882,15 @@ pm_setup_args_dup_rest_p(const pm_node_t *node)
}
case PM_IMPLICIT_NODE:
return pm_setup_args_dup_rest_p(((const pm_implicit_node_t *) node)->value);
case PM_ARRAY_NODE: {
const pm_array_node_t *cast = (const pm_array_node_t *) node;
for (size_t index = 0; index < cast->elements.size; index++) {
if (pm_setup_args_dup_rest_p(cast->elements.nodes[index])) {
return true;
}
}
return false;
}
default:
return true;
}

View File

@ -807,6 +807,13 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(0, 1, "keyword(*empty_array, a: ->{}#{block})") # LAMBDA
check_allocations(0, 1, "keyword(*empty_array, a: $1#{block})") # NTH_REF
check_allocations(0, 1, "keyword(*empty_array, a: $`#{block})") # BACK_REF
# LIST: Only 1 array (literal [:c]), not 2 (one for [:c] and one for *empty_array)
check_allocations(1, 1, "keyword(*empty_array, a: empty_array, b: [:c]#{block})")
check_allocations(1, 1, "keyword(*empty_array, a: empty_array, b: [:c, $x]#{block})")
# LIST unsafe: 2 (one for [Object()] and one for *empty_array)
check_allocations(2, 1, "keyword(*empty_array, a: empty_array, b: [Object()]#{block})")
check_allocations(2, 1, "keyword(*empty_array, a: empty_array, b: [:c, $x, Object()]#{block})")
RUBY
end
@ -877,6 +884,13 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(0, 1, "keyword.(*empty_array, a: ->{}#{block})") # LAMBDA
check_allocations(0, 1, "keyword.(*empty_array, a: $1#{block})") # NTH_REF
check_allocations(0, 1, "keyword.(*empty_array, a: $`#{block})") # BACK_REF
# LIST safe: Only 1 array (literal [:c]), not 2 (one for [:c] and one for *empty_array)
check_allocations(1, 1, "keyword.(*empty_array, a: empty_array, b: [:c]#{block})")
check_allocations(1, 1, "keyword.(*empty_array, a: empty_array, b: [:c, $x]#{block})")
# LIST unsafe: 2 (one for [:c] and one for *empty_array)
check_allocations(2, 1, "keyword.(*empty_array, a: empty_array, b: [Object()]#{block})")
check_allocations(2, 1, "keyword.(*empty_array, a: empty_array, b: [:c, $x, Object()]#{block})")
RUBY
end