fix(core): make ${_param.…} substitution single-pass and order-independent - #3101
fix(core): make ${_param.…} substitution single-pass and order-independent#3101phil-opp wants to merge 1 commit into
${_param.…} substitution single-pass and order-independent#3101Conversation
…pendent
substitute_params_in_str looped over the params map calling
String::replace on the accumulating result. That made expansion depend on
BTreeMap key ordering and let a parameter value that itself contained a
`${_param.…}` token (or a literal one the user wanted to keep) be expanded
transitively — e.g. with a="${_param.b}", b="x", the arg `${_param.a}`
expanded all the way to `x` only because `a` sorts before `b`.
Replace it with a single left-to-right scan that substitutes each
`${_param.<key>}` token once and never re-examines substituted text, so
substitution is simultaneous and order-independent. Unknown keys are left
verbatim, matching the previous behavior of only replacing keys present in
the map. Adds unit tests for the basic, unknown-key, dangling-prefix, and
non-transitive/order-independent cases.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1Fmp8pELuTiPGTStomkZj
|
Merging to
After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here |
|
🤖 Automated review by Claude Code — fully automated review, not vetted by a human. No issues found. The rewritten One behavioral note, not a blocker: a value that itself contains a Generated by Claude Code |
Issue
substitute_params_in_nodeexpands${_param.<key>}references in a module node's args viasubstitute_params_in_str(libraries/core/src/descriptor/expand.rs):Because each
replaceruns over the output of the previous iteration, the result is order-dependent (onBTreeMapkey order) and transitively recursive: a parameter whose value contains another${_param.…}token — or a literal one the user wanted to preserve — gets expanded again.Concretely, with
a = "${_param.b}"andb = "x":asorts beforeb, so${_param.a}becomes${_param.b}and thenx. Swap the names and it does not expand. A user value that legitimately contains the literal text${_param.something}is silently corrupted.Fix
Replace the chained-
replaceloop with a single left-to-right scan that substitutes each${_param.<key>}token exactly once and continues after the substituted region, so substituted values are never re-scanned. This makes substitution simultaneous and order-independent. Unknown keys are left verbatim (matching the old "only replace keys present in the map" behavior), and a dangling${_param.with no closing brace is emitted literally with guaranteed loop progress.No change for the ordinary case (
--speed ${_param.speed}→--speed 2.0).Validation
expand_params_in_args/expand_params_in_envtests still pass.cargo +1.97.1 fmt -p dora-core -- --check— clean.cargo +1.97.1 clippy -p dora-core -- -D warnings— clean.cargo +1.97.1 test -p dora-core substitute_params/expand_params— all pass.🤖 Generated with Claude Code
Generated by Claude Code