Re: PATCH: ::= didn't respect parameter flags
Bart Schaefer <[email protected]> Wed, 10 Jun 2026 14:25:40 -0700
| Newsgroups | gmane.comp.shells.zsh.devel |
|---|---|
| Message-ID | <CAH+w=7Zc3w==i=cEgnwx5BDwEOyK-YErh3FOLwynsS+iU_oq3Q@mail.gmail.com> |
On Wed, Jun 10, 2026 at 9:17 AM Mikael Magnusson <[email protected]> wrote: > > If you, or anyone, have a specific change to suggest to the list of > rules, that would be nice. It depends on where in the sequence this occurs. E.g. "internal flags" (typeset -u in your examples) are supposed to be applied very early (rule #2). The only thing that happens before that is nested substitution (described in rule #1 as ${...}) where it's explicitly stated that "The flags are not propagated up to enclosing substitutions". Also the -u flag explicitly says it's applied "when the parameter is expanded". I believe rule #2 is meant to generalize this to all of -l, -i, -E, etc. So if "the value of the parameter is THEN substituted" and foo has the -u flag, the expansion of ${foo::=bar} would be "BAR", not "bar". The insertion of (U) into the expansion should make no difference, it doesn't happen until step #12 (redundant with #2 in this example). On the other hand, ${(L)foo::=bar} should override the -u flag and expand to "bar". Even with your patch applied, this looks like it is what happens: ( typeset -u foo=bar print ${foo::=${bar=lower}} print ${(L)foo::=${bar=lower}} typeset -p foo bar ) LOWER lower typeset -u foo=lower typeset bar=lower This has more significance for the ${foo=bar} and ${foo:=bar} cases where the order of operations would determine whether $foo is unset or empty. Next consider ${foo=${bar::=lower}. If the assignment to foo does not happen, the assignment to bar doesn't happen either. This indicates that the right-hand-side is NOT in fact a "nested expansion" per rule #1, but instead is a whole new application of the "Rules" just before the assignment occurs (in fact everything to the right is a simple string replacement). However, since ${(A)=ary::=$string} applies word splitting to $string before performing the array assignment, expansion of $string must take place before step #11, yet assignment doesn't happen until after that. Odder still, ${(AzOu)=ary::=$string} expands in unique reverse-sorted order but assigns in raw split order , in which case rule #2 should have already been applied and ${foo::=bar} should be "bar", but it isn't -- except in the examples where you throw in (P), which invokes rule #4. Further, ${(AzOu)ary::=$string} assigns before splitting and creates a single element array, even though it substitutes as a split array. The assignment split is done by ${=ary} but the result split is done by ${(z)ary}. So that would imply that assignment takes place between steps #11 and #12. (I haven't dug into steps #13-#16.) But that contradicts the first part about rule #2. The phrase "is THEN substituted" is ambiguous in the description of the assignment forms. Operationally, it looks like we start over again at rule #1 after deciding it's necessary to perform an assignment? ( foo=bar echo ${${foo}=fred} echo ${${foo}:=fred} { echo ${${foo}::=fred} } always { TRY_BLOCK_ERROR=0; } echo ${(P)${foo}::=fred} echo $bar ) bar bar zsh: not an identifier: fred fred The result of the nested expansion is tested to determine unset/empty, and then assignment is attempted.