Zsh Mailing List Archive
Messages sorted by:
Reverse Date,
Date,
Thread,
Author
[PATCH] Fix "private" options
- X-seq: zsh-workers 55178
- From: Philippe Altherr <philippe.altherr@xxxxxxxxx>
- To: Zsh hackers list <zsh-workers@xxxxxxx>
- Subject: [PATCH] Fix "private" options
- Date: Fri, 25 Sep 2026 02:03:09 +0200
- Arc-authentication-results: i=1; mx.google.com; arc=none
- Arc-message-signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=to:subject:message-id:date:from:mime-version:dkim-signature; bh=ZWBY9JX/bj3urwNKH7xjjf08H9M75hSk1pS0Tkc7C4Q=; fh=BgAYDYpL6Ne/A5nWEMVJiHiBtrz8Imz3uf26RDwgQX4=; b=G5GczarqSYpuZEPtoJOavMYTo+h5Sy9raGDqS4GNha8W1kw+n+55cr0feJ4U9GFtS5 /l4YDJrzSd0VLCh6WE/GmbQsWW7ErBVQWrmv5znCco9iosuPaM5Nlxbvtbz83zbTs9Vx pJH7Ql5YAuPLBeNyM60td5HDkGgnqPgi4o60H6Q7bitlwdw6BHS1WAzDB3j2gf3jmEOz 88ln5+G157nsVt7sIJpQuYqochWqlwQoA/zCrbD5wMYuWcdHbyO63Ofl5P8snaKOpKHI wiU3BcNcdIytTPKTX1tCYlOhXQkVGWL6KbfTBTCHLssEnl7hu534KyEoL8OS/gElLDDY vYzw==; darn=zsh.org
- Arc-seal: i=1; a=rsa-sha256; t=1790294601; cv=none; d=google.com; s=arc-20260327; b=aPREjXQ4XxSC3aKEBlEgWQ2ZQDD4YSZMTKE/KdAQ1afwtGRzTT6ZmSdMFlOqhuivB0 qW+MgPCtjdxaQw8fMeObzx8VrYLB+xiWlcfP/MkFVx05RUwy9qCgwFDA7YTKtci0ck25 e/xR85HJ8optFqH7Otf/A86KDmXIvJVw6nMmXPwHUwFup+6yAeo0xrZpcIlSt08KnLyB yaNRgudpxilLx7kMcA1g3PFymqFUxBzmADj5QM/YCzBvN4X/eQTB91JpGQtg3QPyXBq3 BSxiByC9STeewKglnKYTUHBcQ3IxI//dBxF5klM4KCsAMUKAzauQ9eYhG2A9xcCF5bm7 csGw==
- Archived-at: <https://zsh.org/workers/55178>
- List-id: <zsh-workers.zsh.org>
The current implementation of the "private" builtin doesn't support the "-p" option but supports the "-m" option, which is the exact opposite of other typeset-like builtins like "local", "integer", or "float". This isn't a problem per se. However, the
"zsh/param/private" module hijacks the "local" builtin in order to give it support for the "-P" option. This implies that as soon as the module is loaded, the "local" builtin loses its support for "-p" (and gains support for "-m"). The patch remedies this by adding support for "-p" and removing support for "-m".
Invoking "private" (or "local -P") with no parameter names should print all private parameters. Filtering by private parameters is however currently not supported. The patch therefore changes "private" (and "local -P") to require at least one parameter name.
The current implementation of "private" contains logic that assumes that the "+" option should trigger the printing of any listed parameter name. This is bogus; "+" is not supposed to trigger printing; under some circumstances. The patch removes this logic. This changes the behavior of "private + var", which until now printed the parameter "var" if it already existed or else created a plain scalar "var" and now is equivalent to "private var", which creates a private parameter "var" or tries to turn any already existing parameter "var" into a private one.
The patch also removes some paranoid logic for recursive calls that don't exist.
The test file V10private contains a test case that runs a modified version of the test file B02typeset where the module "zsh/param/private" has been loaded. This is to ensure that the "local" builtin, which the module hijacks, still works as expected. A problem with this test case is that when the modified test file fails, the only information we get is that the test case failed. There are no details on which test cases in the modified B02typeset test file failed and why. To remedy this, the patch removes the test case and adds the generation of the modified test file to the Makefile. The modified test file then runs like any normal test file.
Philippe
diff --git a/.gitignore b/.gitignore
index b8922ef6f..d87312451 100644
--- a/.gitignore
+++ b/.gitignore
@@ -153,6 +153,7 @@ Src/Zle/widgets.list
Src/Zle/zle_things.h
Src/Zle/zle_widget.h
+Test/V10private-B02typeset.ztst
Test/*.tmp
/.project
.DS_Store
diff --git a/Doc/Zsh/mod_private.yo b/Doc/Zsh/mod_private.yo
index 20b0a1f3b..8d6b090bc 100644
--- a/Doc/Zsh/mod_private.yo
+++ b/Doc/Zsh/mod_private.yo
@@ -10,16 +10,20 @@ ifnzman()
startitem()
findex(private)
cindex(private parameter, creating)
-item(tt(private) [ {tt(PLUS())|tt(-)}tt(AHUahlmrtux) ] \
-[ {tt(PLUS())|tt(-)}tt(EFLRZi) [ var(n) ] ] [ var(name)[tt(=)var(value)] ... ])(
+xitem(tt(private) [ {tt(PLUS())|tt(-)}tt(AHUahlrtux) ] \
+[ {tt(PLUS())|tt(-)}tt(EFLRZip) [ var(n) ] ] \
+[ tt(+) ] var(name)[tt(=)var(value)] ...)
+item(tt(private) {tt(PLUS())|tt(-)}tt(n) [ {tt(PLUS())|tt(-)}tt(Hhrtu) ] \
+[ {tt(PLUS())|tt(-)}tt(p) [ var(n) ] ] \
+[ tt(+) ] var(name)[tt(=)var(value)] ...)(
The tt(private) builtin accepts all the same options and arguments as tt(local)
(nmref(Shell Builtin Commands)(zshbuiltins)) except
-for the `tt(-)tt(T)' option. Tied parameters may not be made private.
+for the `tt(-T)' option. Tied parameters may not be made private.
-The `tt(-)tt(p)' option is presently disabled because the state of
-private parameters cannot reliably be reloaded. When `tt(typeset -)tt(p)'
+Querying for private parameters is presently not supported. At least
+one parameter name must be provided. When `tt(typeset -p)'
outputs a private parameter, it is treated as a local with the
-`tt(-)tt(h)' (hide) option enabled.
+`tt(-h)' (hide) option enabled.
If used at the top level (outside a function scope), tt(private) creates a
normal parameter in the same manner as tt(declare) or tt(typeset). A
@@ -31,7 +35,7 @@ declared with tt(local), except having special properties noted below.
Special parameters which expose or manipulate internal shell state, such
as tt(ARGC), tt(argv), tt(COLUMNS), tt(LINES), tt(UID), tt(EUID), tt(IFS),
tt(PROMPT), tt(RANDOM), tt(SECONDS), etc., cannot be made private unless
-the `tt(-)tt(h)' option is used to hide the special meaning of the
+the `tt(-h)' option is used to hide the special meaning of the
parameter. This may change in the future.
)
enditem()
diff --git a/INSTALL b/INSTALL
index da2625b43..1f75995a2 100644
--- a/INSTALL
+++ b/INSTALL
@@ -180,14 +180,14 @@ After configuring, to build zsh, execute the command:
make
It's then a good idea to check that your build is working properly:
- make check
+ make -s check
If you have trouble with a particular test, you can run it separately:
- make TESTNUM=C02 check
+ make -s TESTNUM=C02 check
The TESTNUM value can be a single test number, as above, or a letter to
run an entire category of tests:
- make TESTNUM=Y check
+ make -s TESTNUM=Y check
See Test/README for a list of test categories.
diff --git a/Src/Modules/param_private.c b/Src/Modules/param_private.c
index e06cbb1de..c9f19ec1c 100644
--- a/Src/Modules/param_private.c
+++ b/Src/Modules/param_private.c
@@ -210,50 +210,47 @@ is_private(Param pm)
return 1;
}
-static int fakelevel;
+static int fakelevel = 0;
/**/
static int
bin_private(char *nam, char **args, LinkList assigns, Options ops, int func)
{
- int from_typeset = 1;
- int ofake = fakelevel; /* paranoia in case of recursive call */
- int hasargs = /* *args != NULL || */ (assigns && firstnode(assigns));
- makeprivate_error = 0;
+ int errors;
if (!OPT_ISSET(ops, 'P')) {
- fakelevel = 0;
- from_typeset = bin_typeset(nam, args, assigns, ops, func);
- fakelevel = ofake;
- return from_typeset;
+ return bin_typeset(nam, args, assigns, ops, func);
} else if (OPT_ISSET(ops, 'T')) {
- zwarn("bad option: -T");
+ zwarnnam(nam, *nam == 'l' ?
+ "-T not allowed with -P" : "bad option: -T");
return 1;
- }
-
- if (locallevel == 0) {
+ } else if (!*args && !(assigns && firstnode(assigns))) {
+ zwarnnam(nam, *nam == 'l' ?
+ "at least one parameter name is required with -P" :
+ "at least one parameter name is required");
+ return 1;
+ } else if (OPT_ISSET(ops, 'p')) {
+ return bin_typeset(nam, args, assigns, ops, func);
+ } else if (locallevel == 0) {
if (isset(WARNCREATEGLOBAL))
zwarnnam(nam, "invalid local scope, using globals");
- return bin_typeset("private", args, assigns, ops, func);
+ return bin_typeset(nam, args, assigns, ops, func);
}
- if (!(OPT_ISSET(ops, 'm') || OPT_ISSET(ops, '+')))
- ops->ind['g'] = 2; /* force bin_typeset() to behave as "local" */
- if (OPT_ISSET(ops, 'p') || OPT_ISSET(ops, 'm') ||
- (!hasargs && OPT_ISSET(ops, '+'))) {
- return bin_typeset("private", args, assigns, ops, func);
- }
+ ops->ind['g'] = 2; /* force bin_typeset() to behave as "local" */
queue_signals();
fakelevel = locallevel;
startparamscope();
- from_typeset = bin_typeset("private", args, assigns, ops, func);
+ errors = bin_typeset(nam, args, assigns, ops, func);
+ makeprivate_error = 0;
scanhashtable(realparamtab, 0, 0, 0, makeprivate, 0);
+ errors |= makeprivate_error;
endparamscope();
- fakelevel = ofake;
+ fakelevel = 0;
unqueue_signals();
- return makeprivate_error | from_typeset;
+ return errors;
}
static void
@@ -649,7 +646,7 @@ printprivatenode(HashNode hn, int printflags)
static struct builtin bintab[] = {
/* Copied from BUILTIN("local"), "P" added */
- BUILTIN("private", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_private, 0, -1, 0, "AE:%F:%HL:%PR:%TUZ:%ahi:%lnmrtux", "P")
+ BUILTIN("private", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_private, 0, -1, 0, "AE:%F:%HL:%PR:%TUZ:%ahi:%lnp:%rtux", "P")
};
static struct features module_features = {
diff --git a/Test/B02typeset.ztst b/Test/B02typeset.ztst
index 6bc53c68a..ece825156 100644
--- a/Test/B02typeset.ztst
+++ b/Test/B02typeset.ztst
@@ -29,8 +29,12 @@
# that code, as that would affect the scoping.
%prep
- ## Do not remove the next line, it's used by V10private.ztst
- # test_zsh_param_private
+ load-private-module() {
+ if ! zmodload zsh/param/private 2>/dev/null; then
+ ZTST_unimplemented="can't load the zsh/param/private module for testing"
+ fi
+ }
+ # load-private-module # Needed for V10private-B02typeset.ztst
mkdir typeset.tmp && cd typeset.tmp
@@ -149,10 +153,22 @@
>array local array
>scalar a r r a y
+ declare -p scalar
+ declare -m scalar
declare +m scalar
-0:declare previously lacked -m/+m options
+0:declare supports -p and -m/+m options
+>typeset -g scalar=scalar
+>scalar=scalar
>scalar
+ local -p scalar
+ local -m scalar
+ local +m scalar
+1:local supports -p but not -m/+m options
+>typeset -g scalar=scalar
+?(eval):local:2: bad option: -m
+?(eval):local:3: bad option: +m
+
scope10
print $outer
0:Global export
diff --git a/Test/Makefile.in b/Test/Makefile.in
index 09f37bf53..12a85c0ff 100644
--- a/Test/Makefile.in
+++ b/Test/Makefile.in
@@ -40,9 +40,23 @@ INSTALL = @INSTALL@
# ========== DEPENDENCIES FOR TESTING ==========
+V10private-B02typeset.ztst: B02typeset.ztst
+ TEXT="# load-private-module #"; \
+ if ! grep -q "$$TEXT" $<; then \
+ echo "$<: Failed to generate $@, text \"$$TEXT\" is missing" >&2; \
+ exit 1; \
+ fi; \
+ { \
+ echo '######################################################################'; \
+ echo '# DO NOT EDIT - Generated from $< by make'; \
+ echo '######################################################################'; \
+ echo ''; \
+ sed -e "s,$$TEXT.*,load-private-module," < $<; \
+ } > $@
+
# If ZTST_handler is not empty, run that instead of runtests.zsh.
# It's assumed to be found in the source directory.
-check test:
+check test: V10private-B02typeset.ztst
if test -n "$(DLLD)"; then \
cd $(dir_top) && DESTDIR= \
$(MAKE) MODDIR=`pwd`/$(subdir)/Modules install.modules > /dev/null; \
@@ -68,7 +82,7 @@ check test:
@CLEAN_MK@
mostlyclean-here:
- rm -rf Modules .zcompdump *.tmp
+ rm -rf Modules .zcompdump *.tmp V10private-B02typeset.ztst
distclean-here:
rm -f Makefile
diff --git a/Test/README b/Test/README
index b9d393d7c..0eac9daf8 100644
--- a/Test/README
+++ b/Test/README
@@ -14,24 +14,26 @@ scripts names:
Y: completion
Z: separate systems and user contributions
-You will need to run these by using `make test' in the Test subdirectory of
-the build area for your system (which may or may not be the same as the
-Test subdirectory of the source tree), or the directory above. You can get
-more information about the tests being performed with
- ZTST_verbose=1 make check
-(`test' is equivalent to `check') or change 1 to 2 for even more detail.
+You will need to run these by using `make -s check' in the Test
+subdirectory of the build area for your system (which may or may not
+be the same as the Test subdirectory of the source tree), or the
+directory above. The `-s' option tells `make' not to print the
+commands it runs. You can get more information about the tests being
+performed with
+ ZTST_verbose=1 make -s check
+or change 1 to 2 for even more detail.
A test file is usually aborted on the first error. To continue to the
end, run with
- ZTST_continue=1 make check
+ ZTST_continue=1 make -s check
This can usefully be combined with ZTST_verbose. The test is always
aborted on a syntax error as in that case it is not obvoius how to
continue.
Individual or groups of tests can be performed with
- make TESTNUM=C02 check
+ make -s TESTNUM=C02 check
or
- make TESTNUM=C check
+ make -s TESTNUM=C check
to perform just the test beginning C02, or all tests beginning C,
respectively.
diff --git a/Test/V10private.ztst b/Test/V10private.ztst
index f3ad7b6b9..7624a1d7d 100644
--- a/Test/V10private.ztst
+++ b/Test/V10private.ztst
@@ -4,10 +4,6 @@
if ! zmodload zsh/param/private 2>/dev/null; then
ZTST_unimplemented="can't load the zsh/param/private module for testing"
- else
- # Do not use .tmp here, ztst.zsh will remove it too soon (see %cleanup)
- mkdir private.TMP
- sed -e 's,# test_zsh_param_private,zmodload zsh/param/private,' < $ZTST_srcdir/B02typeset.ztst > private.TMP/B02
fi
setopt TYPESET_TO_UNSET
@@ -17,6 +13,35 @@
(zmodload -u zsh/param/private && zmodload zsh/param/private)
0:unload and reload the module without crashing
+ () {
+ private VAR1=ok
+ local -P VAR2=ok
+ private -T VAR=KO var 2>&1
+ local -P -T VAR=KO var 2>&1
+ private -p 2>&1
+ local -P -p 2>&1
+ private -m 2>&1
+ local -P -m 2>&1
+ private +m 2>&1
+ local -P +m 2>&1
+ private + 2>&1
+ local -P + 2>&1
+ typeset -p VAR1 VAR2
+ }
+0:private/local option compatibility
+>(anon):private:3: bad option: -T
+>(anon):local:4: -T not allowed with -P
+>(anon):private:5: at least one parameter name is required
+>(anon):local:6: at least one parameter name is required with -P
+>(anon):private:7: bad option: -m
+>(anon):local:8: bad option: -m
+>(anon):private:9: bad option: +m
+>(anon):local:10: bad option: +m
+>(anon):private:11: at least one parameter name is required
+>(anon):local:12: at least one parameter name is required with -P
+>typeset -h VAR1=ok
+>typeset -h VAR2=ok
+
typeset scalar_test=toplevel
() {
print $scalar_test
@@ -118,14 +143,16 @@
}
outer () {
local -PA hash_test=(in function)
- private + hash_test
+ typeset -p hash_test
+ print ${(t)hash_test} ${(kv)hash_test}
inner
}
outer
print ${(kv)hash_test}
0:private hides value from surrounding scope in nested scope
>typeset -a hash_test=( top level )
->hash_test=( [in]=function )
+>typeset -Ah hash_test=( [in]=function )
+>association-local-hide-special in function
>typeset -g -a hash_test=( top level )
>array-local top level
>top level
@@ -292,15 +319,6 @@ F:future revision will create a global with this assignment
() { private -h SECONDS }
0:private parameter may hide a special parameter
- if (( UID )); then
- ZTST_verbose=0 $ZTST_exe +Z -f $ZTST_srcdir/ztst.zsh private.TMP/B02
- else
- ZTST_skip="cannot re-run typeset tests when tests run as superuser"
- fi
-0:typeset still works with zsh/param/private module loaded
-*>*
-*>*
-
typeset top=TOP
() {
local -P -n test=top
@@ -605,8 +623,3 @@ F:Better if caught in checkclobberparam() but exec.c doesn't know scope
}
1:function may not access private declared in nofork
*?*: q: can't modify read-only parameter
-
-%clean
-
- unsetopt TYPESET_TO_UNSET
- rm -r private.TMP
Messages sorted by:
Reverse Date,
Date,
Thread,
Author