Below is a patch that changes Zsh's emulation of ksh to map ksh's ! to just (k) instead of (!k). Note that the flag combination (!k) is forbidden, which I think is right.
Here is the ksh script that I used to compare Zsh's before and after patch ksh emulation:
typeset str0=abc
typeset -a arr0=(aa bb cc)
typeset -A hsh0=([0]=aa [1]=bb [2]=cc)
typeset -n str1=str0 str2=str1
typeset -n arr1=arr0 arr2=arr1
typeset -n hsh1=hsh0 hsh2=hsh1
# Regular expansions
echo "${str0} - ${str1} - ${str2}"
echo "${str0[1]} - ${str1[1]} - ${str2[1]}"
echo "${str0[1,2]} - ${str1[1,2]} - ${str2[1,2]}"
echo "${str0[@]} - ${str1[@]} - ${str2[@]}"
echo "${arr0} - ${arr1} - ${arr2}"
echo "${arr0[1]} - ${arr1[1]} - ${arr2[1]}"
echo "${arr0[1,2]} - ${arr1[1,2]} - ${arr2[1,2]}"
echo "${arr0[@]} - ${arr1[@]} - ${arr2[@]}"
echo "${hsh0} - ${hsh1} - ${hsh2}"
echo "${hsh0[1]} - ${hsh1[1]} - ${hsh2[1]}"
echo "${hsh0[@]} - ${hsh1[@]} - ${hsh2[@]}"
# Bang expansions
echo "${!str0} - ${!str1} - ${!str2}"
echo "${!str0[1]} - ${!str1[1]} - ${!str2[1]}"
echo "${!str0[1,2]} - ${!str1[1,2]} - ${!str2[1,2]}"
echo "${!str0[@]} - ${!str1[@]} - ${!str2[@]}"
echo "${!arr0} - ${!arr1} - ${!arr2}"
echo "${!arr0[1]} - ${!arr1[1]} - ${!arr2[1]}"
echo "${!arr0[1,2]} - ${!arr1[1,2]} - ${!arr2[1,2]}"
echo "${!arr0[@]} - ${!arr1[@]} - ${!arr2[@]}"
echo "${!hsh0} - ${!hsh1} - ${!hsh2}"
echo "${!hsh0[1]} - ${!hsh1[1]} - ${!hsh2[1]}"
echo "${!hsh0[@]} - ${!hsh1[@]} - ${!hsh2[@]}"
Let's first have a look at regular expansions:
| Zsh's ksh emulation | ksh | Zsh's ksh emulation |
| ${!var} <=> ${(!k)var} |
| ${!var} <=> ${(k)var} |
str str[1] str[1,2] str[@]
arr arr[1] arr[1,2] arr[@]
hsh hsh[1] hsh[@] | abc - abc - abc b - b - b bc - bc - bc abc - abc - abc
aa - aa - aa bb - bb - bb bb cc - bb cc - bb cc aa bb cc - aa bb cc - aa bb cc
aa - aa - aa bb - bb - bb aa bb cc - aa bb cc - aa bb cc | abc - abc - abc - - - - abc - abc - abc
aa - aa - aa bb - bb - bb cc - cc - cc aa bb cc - aa bb cc - aa bb cc
aa - aa - aa bb - bb - bb aa bb cc - aa bb cc - aa bb cc | abc - abc - abc b - b - b bc - bc - bc abc - abc - abc
aa - aa - aa bb - bb - bb bb cc - bb cc - bb cc aa bb cc - aa bb cc - aa bb cc
aa - aa - aa bb - bb - bb aa bb cc - aa bb cc - aa bb cc |
I highlighted in green the results that match the ksh results. As expected, the results are the same before and after the patch but there are already cases where they don't match the ksh results. That's because in ksh, strings are NOT equated to arrays of characters and thus subscripts other than [@] and [*] don't produce any result. Furthermore, in ksh, ${var[A,B]} is equivalent to ${var[((A,B))]} and thus still returns a single element rather than a range of elements like in Zsh.
Now let's see what happens for bang expansions:
| Zsh's ksh emulation | ksh | Zsh's ksh emulation |
| ${!var} <=> ${(!k)var} |
| ${!var} <=> ${(k)var} |
!str !str[1] !str[1,2] !str[@]
!arr !arr[1] !arr[1,2] !arr[@]
!hsh !hsh[1] !hsh[@] | abc - str0 - str1 b - t - t bc - tr - tr abc - str0 - str1
aa - arr0 - arr1 1 - r - r <error> - rr - rr aa bb cc - arr0 - arr1
aa - hsh0 - hsh1 1 - s - s 0 1 2 - hsh0 - hsh1 | str0 - str0 - str0 str0[1] - str1[1] - str2[1] str0[1,2] - str1[1,2] - str2[1,2] 0 - 0 - 0
arr0 - arr0 - arr0 arr0[1] - arr0[1] - arr0[1] arr0[2] - arr0[2] - arr0[2] 0 1 2 - 0 1 2 - 0 1 2
hsh0 - hsh0 - hsh0 hsh0[1] - hsh0[1] - hsh0[1] 0 1 2 - 0 1 2 - 0 1 2 | abc - abc - abc b - b - b bc - bc - bc abc - abc - abc
aa - aa - aa 1 - 1 - 1 <error> - <error> - <error> aa bb cc - aa bb cc - aa bb cc
aa - aa - aa 1 - 1 - 1 0 1 2 - 0 1 2 - 0 1 2 |
I highlighted in orange the results where Zsh does something similar (return the index/key) but not in the exact same way as ksh (Zsh returns just the index/key, while ksh returns the full parameter spec). The <error> string indicates cases where the expansion fails with an error.
We can see that there are very few cases, either before or after the patch, where Zsh's emulation matches ksh. In my opinion the results with the patch are more compelling. It also leaves open the possibility to have more matches in the future. Although Bart says it would be difficult, maybe we can extend (k) to array ranges, or at least to [@] and [*]. With that the last line of the array group would match. Another possibility is to extend (k), at least in ksh emulation, to also work for plain values. This looks feasible to me. The value structure computed by expansions typically contains a reference to the expanded parameter (the referred one, not the starting one), from which one can access its name. With that, the first line of each group would match.