The second part of the test whose expected result your patch changed:
() { typeset -n foo; foo=zz; local zz; foo=zz; print $bar $zz }
should in fact assign local zz=zz via the foo reference, because foo
is not an "upper" reference there. Why did you think otherwise?
Here is the test example with comments showing what I think should happen:
bar=xx
typeset -n foo=bar
() {
typeset -n foo; # Creates a local "foo" placeholder ref.
foo=zz; # Initializes the local "foo" placeholder ref with "zz" (the name of a not-yet-defined variable).
foo=zz || print -u2 foo: assignment failed; # Creates the global variable "zz" and initializes it with "zz".
print $bar $zz; # Prints the content of the global variable "bar" and "zz", which results in "xx zz".
}
() {
typeset -n foo; # Creates a local "foo" placeholder ref.
foo=zz; # Initializes the local "foo" placeholder ref with a reference to the global "zz" variable.
local zz; # Creates a local "zz" variable initialized with "".
foo=zz; # Assigns "zz" to the global variable "zz".
print $bar $zz; # Prints the content of the global variable "bar" and the local variable "zz", which results in "xx".
}
Note that the global nameref "foo" isn't involved at all. Commenting it out produces the same result. The only effect of the global "foo" is to confuse the current implementation and make it output an incorrect result. If it's commented out, the current implementation produces the same result as my patch.
Here is what happens. The second "foo=zz" in the first function triggers a call to createparam with name="foo" and flags=0 (because the code for the assignment figured that "foo" didn't refer to any variable, it proceeds to first create the global variable referred to by "foo" to then assign "zz" to that variable). The call to createparam first looks up "foo" and finds the local "foo" defined in the function, which it stores in oldpm. The current code then updates oldpm with upscope(oldpm, oldpm->base), which returns the global "foo". Next, createparam checks whether oldpm refers to a nameref that refers to a not-yet-defined variable. It finds none (lastpm = NULL) and thus proceeds to create a global variable named "foo", which fails because there is already one.
With my patch, the initial oldpm is not updated. Thus, lastpm gets initialized with the local nameref "foo" and this leads createparam to update the name of the variable to create with "zz". Since there is no "zz" variable, createparam successfully creates one and the assignment then initializes it with "zz".
Here is a commented version of my example that lead to the discovery of the bug:
typeset -n ref=var1
() {
typeset -n ref=var2; # Creates a local "foo" ref.
ref=RESET # Creates a global variable "var2" and initializes it with "RESET".
typeset -p ref var2
}
typeset -p ref var2
Here, the problem is caused by the call to createparam(name="ref", flags=0) triggered by the statement "ref=RESET". Like above, the current code updates oldpm from the local "ref" to the global "ref". This leads createparam to then create a global variable "var1". Then, the assignment fails to assign "RESET" to any variable because the local "ref" still refers to no variable. With my patch, createparam instead creates the global variable "var2".
More later today or tomorrow...
Philippe