I alluded to this on zsh-users. If you "emulate ksh" and then attempt "typeset -p" (especially "typeset -p -m") the results are somewhat unpredictable, but among the possible effects is that instead of printing parameter names/values, typeset instead attempts to create a local name for every existing global name. In part this invovles the GLOBAL_EXPORT option which is set by default in zsh mode. When it is unset, the PM_LOCAL flag is turned on in cases where it should not be, or at least in cases where typeset_single() doesn't know how to deal with it and bypasses the value printing code to fall through to the assignment / attribute modifying code. It doesn't actually succeed in doing anything at all, but if compiled with ZSH_DEBUG it spits out a lot of repetitions of BUG: -p not handled The doc for GLOBAL_EXPORT says: If this option is set, passing the -x flag to the builtins declare, float, integer, readonly and typeset (but not local) will also set the -g flag; hence parameters exported to the environment will not be made local to the enclosing function, unless they were already or the flag +g is given explicitly. If the option is unset, exported parameters will be made local in just the same way as any other parameter. The code that implements this is: if (!(OPT_ISSET(ops,'g') || OPT_ISSET(ops,'x') || OPT_ISSET(ops,'m')) || OPT_PLUS(ops,'g') || *name == 'l' || (!isset(GLOBALEXPORT) && !OPT_ISSET(ops,'g'))) on |= PM_LOCAL; If we unroll that and apply a little boolean algebra: if (*name == 'l' || OPT_PLUS(ops,'g')) on |= PM_LOCAL; else if (OPT_ISSET(ops,'g')) /* Do nothing */; /* Or strangely: on &= ~PM_LOCAL */ else if (!isset(GLOBALEXPORT)) on |= PM_LOCAL; else if (!(OPT_ISSET(ops,'x') || OPT_ISSET(ops,'m'))) on |= PM_LOCAL; This demonstrates that GLOBALEXPORT (when not set) affects a lot more than just the -x flag. In the "strangely" branch noted above, PM_LOCAL is often still set, but it's state doesn't seem to matter. Anyway, to make this almost match the doc, I can rearrange it to be: if (*name == 'l' || OPT_PLUS(ops,'g')) on |= PM_LOCAL; else { if (!(OPT_ISSET(ops,'x') || OPT_ISSET(ops,'m'))) on |= PM_LOCAL; else if (OPT_MINUS(ops,'x')) { if (isset(GLOBALEXPORT)) ops->ind['g'] = 1; else if (locallevel) on |= PM_LOCAL; } if (OPT_MINUS(ops,'g')) on &= ~PM_LOCAL; } This fixes most cases but it's still possible to generate the "BUG" message by explicitly enabling PM_LOCAL: % () { typeset -p +g -m \* } And in that case it somehow wipes out all the prompt-related parameters (PS1, PS2, etc.). The answer to that seems to be to never go past the "if (usepm)" branch in typeset_single() when the 'p' option is set, but that leaves cases that seem like they should work but where "usepm" is turned off too soon. So I've attempted to fix that too. The remaining thing, which I have not been able to fix, is the tangled meanings of [+|-][pgm] ... some combinations that it seems should display something display nothing, and some combinations display what seems like too much. This is because some decisions are made in bin_typeset() and others are deferred to typeset_single(). For example: % () { typeset +m f\* } array tied FIGNORE fignore array tied FPATH fpath undefined funcfiletrace undefined funcsourcetrace undefined funcstack undefined functions undefined functions_source undefined functrace But add -p and (trimming some long output): % () { typeset -p +m f\* } typeset -g -aT FIGNORE fignore=( ) typeset -g -aT FPATH fpath=( /usr/local/share/zsh/... ) That works exactly the same with -p -m which I guess is OK. However ... % () { local foo=bar; typeset -p -m f\* } typeset -g -aT FIGNORE fignore=( ) typeset -g -aT FPATH fpath=( /usr/local/share/zsh/... ) typeset foo=bar % () { local foo=bar; typeset -p +m f\* } typeset -g -aT FIGNORE fignore=( ) typeset foo=bar typeset -g -aT FPATH fpath=( /usr/local/share/zsh/... ) The order of printing changes between -/+ if there's a local that matches? Those two cases also don't matter for +p with either state of m, but then there's [+/-]g ... it seems like (inside a function where -g matters): typeset -p -g ... should print only globals and skip locals, but that doesn't happen. Also typeset +p +g ... should have the same effect, but it does something else. And typeset -p +g ... should print only locals, but it prints everything except in typeset -p +g -m ... which does print only locals, whereas typeset -p +g +m ... prints nothing. And you'd think that typeset +p -g ... would be the same as -p +g, but in fact it also prints everything (ignoring -m entirely), but becomes the same if +m is added. On top of this are differences between [-/+]m with and without a pattern argument. To even start fixing this mess, we'd have to explain what a function considers "local" and what "global". Are all "inherited" scopes global, so local is the current scope only? Gaah. Anyway the attached at least prevents munging the parameter space when the intention is only to display it.