As of this commit when a tied array is declared but neither the scalar
nor the array has an initializer, the array is initialized to empty.
The scalar struct param of a tied pair stores a direct pointer to the
internal array value of the array struct param, and upon assignment
modifies it without referring to the containing struct. This means
that there's no opportunity to clear the PM_DECLAREDNULL bits on both
structs when the scalar is assigned. Conversely, assigning to the
array does use the struct for the scalar.
This addresses the issue that "typeset foo" creates $foo set to an
empty string, which differs from typeset handling in bash and ksh.
It does this by concealing the internal value rather than alter
the way internal values are defaulted.
This imposes the following changes:
A typeset variable with no assignment triggers NO_UNSET warnings
when the name is used in parameter expansion or math.
The typeset -AEFHLRTZailux options are applied upon the first
assignment to the variable. Explicit unset before the first
assignment discards all of those properties. If any option is
applied to an existing name, historic behavior is unchanged.
Consequent to the foregoing, the (t) parameter expansion flag
prints nothing until after the first assignment, and the (i)
and (I) subscript flags also print nothing.
The bash behavior of "unset foo; typeset -p foo" is NOT used.
This is called out as an emulation distinction, not a change.
The test cases have not been updated, so several now fail.
The test harness has been updated to declare default values.
zsh typically runs the final command in a pipeline in the main shell
instead of a subshell. However, POSIX specifies that all commands in a
pipeline run in a subshell, but permits zsh's behavior as an extension.
The default /bin/sh implementations on various Linux distros and the
BSDs always use a subshell for all components of a pipeline.
Since zsh may be used as /bin/sh in some cases (such as macOS Catalina),
it makes sense to have the common sh behavior when emulating sh, so do
that by checking for being the final item of a multi-item pipeline and
creating a subshell in that case.
From the comment above execpline(), we know the following:
last1 is a flag that this command is the last command in a shell that
is about to exit, so we can exec instead of forking. It gets passed
all the way down to execcmd() which actually makes the decision. A 0
is always passed if the command is not the last in the pipeline. […]
If last1 is zero but the command is at the end of a pipeline, we pass
2 down to execcmd().
So there are three cases to consider in this code:
• last1 is 0, which means we are not at the end of a pipeline, in which
case we should not change behavior.
• last1 is 1, which means we are effectively running in a subshell,
because nothing that happens due to the exec is going to affect the
actual shell, since it will have been replaced. So there is nothing
to do here.
• last1 is 2, which means our command is at the end of the pipeline, so
in sh mode we should create a subshell by forking.
input is nonzero if the input to this process is a pipe that we've
opened. At the end of a multi-stage pipeline, it will necessarily be
nonzero.
Note that several of the tests may appear bizarre, since most developers
do not place useless variable assignments directly at the end of a
pipeline. However, as the function tests demonstrate, there are cases
where assignments may occur when a shell function is used at the end of
a command. The remaining assignment tests simply test additional cases,
such as the use of local, that would otherwise be untested.
As of this commit when a tied array is declared but neither the scalar
nor the array has an initializer, the array is initialized to empty.
The scalar struct param of a tied pair stores a direct pointer to the
internal array value of the array struct param, and upon assignment
modifies it without referring to the containing struct. This means
that there's no opportunity to clear the PM_DECLAREDNULL bits on both
structs when the scalar is assigned. Conversely, assigning to the
array does use the struct for the scalar.
This addresses the issue that "typeset foo" creates $foo set to an
empty string, which differs from typeset handling in bash and ksh.
It does this by concealing the internal value rather than alter
the way internal values are defaulted.
This imposes the following changes:
A typeset variable with no assignment triggers NO_UNSET warnings
when the name is used in parameter expansion or math.
The typeset -AEFHLRTZailux options are applied upon the first
assignment to the variable. Explicit unset before the first
assignment discards all of those properties. If any option is
applied to an existing name, historic behavior is unchanged.
Consequent to the foregoing, the (t) parameter expansion flag
prints nothing until after the first assignment, and the (i)
and (I) subscript flags also print nothing.
The bash behavior of "unset foo; typeset -p foo" is NOT used.
This is called out as an emulation distinction, not a change.
The test cases have not been updated, so several now fail.
The test harness has been updated to declare default values.