Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

Re: PATCH: ::= didn't respect parameter flags



On Wed, Jun 10, 2026 at 9:17 AM Mikael Magnusson <mikachu@xxxxxxxxx> 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.




Messages sorted by: Reverse Date, Date, Thread, Author