Skip to content

param-op: one substitution-spec splitter, not two (#684) - #751

Merged
berrym merged 1 commit into
masterfrom
fix/684-vector-substitution-split
Aug 16, 2026
Merged

param-op: one substitution-spec splitter, not two (#684)#751
berrym merged 1 commit into
masterfrom
fix/684-vector-substitution-split

Conversation

@berrym

@berrym berrym commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Closes #684.

The defect

${v/p/r} and ${arr[@]/p/r} take the same pattern/replacement spec, so they must split it the same way. The per-element dispatch carried its own copy of that split, and the copy had drifted — its no-separator branch skipped the \// canonicalization:

$ lush -c 'v=a/b/c; arr=(a/b/c); printf "<%s><%s>" "${v//\/}" "${arr[@]//\/}"'
<abc><a/b/c>                    # bash: <abc><abc>

The element path now calls lush_param_op_split_substitution_spec — the function the scalar path already used, exported from param_op.c — and the duplicate is deleted. One spec, one splitter.

The code carried a comment naming this exact divergence and #684, noting the fold "belongs in its own commit". This is that commit. It is also the consolidation the expansion arc exists for: param_op.c is the shared core that both the legacy expander and the word CST call, so the fix lands once for both routes.

Two pre-existing defects found while verifying (filed, not folded in)

Both byte-identical against a build of the parent commit:

Verification

  • tests/integration/test_vector_substitution_spec.c — 17 checks. Twelve compare the scalar and element results against each other as well as against a literal, so a future change that breaks both in the same way still fails. The rest cover the vector, joined and positional forms.
  • 4 of 17 fail against the parent build.
  • Full suite 197/197. ASan 65/65; the new test 1/1 under ASan.

Field count is deliberately not asserted here — that contract belongs to #749, and asserting today's collapsed behavior would lock in the defect.

`${v/p/r}` and `${arr[@]/p/r}` take the same `pattern/replacement` spec, so
they must split it the same way. The per-element dispatch carried its own copy
of that split, and the copy had drifted: its no-separator branch skipped the
`\/` -> `/` canonicalization.

    v=a/b/c; arr=(a/b/c)
    ${v//\/}       -> abc
    ${arr[@]//\/}  -> a/b/c        the slashes survived

The element path now calls lush_param_op_split_substitution_spec -- the
function the scalar path already used, exported from param_op.c for the
purpose -- and the duplicate is deleted. One spec, one splitter; they cannot
drift again. The code even carried a comment naming this divergence and #684,
saying the fold belonged in its own commit. This is that commit.

This is the consolidation the expansion arc exists for: param_op.c is the
shared core both the legacy expander and the word CST call, so the fix lands
once for both routes rather than being copied to a second site.

Two PRE-EXISTING defects surfaced while verifying and are filed rather than
folded in, both byte-identical against a build of the parent commit:

  - #749: a per-element operator collapses `"${arr[@]}"` to ONE field where
    bash and zsh give N. The element VALUES are right and this fix corrected
    them (`<a/b x/y>` became `<ab xy>`); the field boundary is lost separately,
    in the dispatch's result handoff, and affects `#`/`%`/`^^`/`//` alike.
  - #750: the REPLACEMENT half is never unescaped, so `${v//X/\/}` yields
    `a\/b` where bash and zsh give `a/b`. Consistent across both paths -- they
    now agree, they simply agree on the wrong replacement.

tests/integration/test_vector_substitution_spec.c: 17 checks. Twelve compare
the scalar and element results against EACH OTHER as well as a literal, so a
future change that breaks both the same way still fails; the rest cover the
vector, joined and positional forms. 4 fail against the parent build.
@codecov

codecov Bot commented Aug 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@berrym
berrym merged commit 58ada0f into master Aug 16, 2026
7 checks passed
@berrym
berrym deleted the fix/684-vector-substitution-split branch August 16, 2026 12:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

${arr[@]//\/} leaves the slashes the scalar ${v//\/} removes (per-element substitution keeps its own spec split)

1 participant