mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-11-26 02:13:38 +08:00
Add set --function
(#8145)
* Add `set --function` This makes the function's scope available, even inside of blocks. Outside of blocks it's the toplevel local scope. This removes the need to declare variables locally before use, and will probably end up being the main way variables get set. E.g.: ```fish set -l thing if condition set thing one else set thing two end ``` could be written as ```fish if condition set -f thing one else set -f thing two end ``` Note: Many scripts shipped with fish use workarounds like `and`/`or` instead of `if`, so it isn't easy to find good examples. Also, if there isn't an else-branch in that above, just with ```fish if condition set -f thing one end ``` that means something different from setting it before! Now, if `condition` isn't true, it would use a global (or universal) variable of te same name! Some more interesting parts: Because it *is* a local scope, setting a variable `-f` and `-l` in the toplevel of a function ends up the same: ```fish function foo2 set -l foo bar set -f foo baz # modifies the *same* variable! end ``` but setting it locally inside a block creates a new local variable that shadows the function-scoped variable: ```fish function foo3 set -f foo bar begin set -l foo banana # $foo is banana end # $foo is bar again end ``` This is how local variables already work. "Local" is actually "block-scoped". Also `set --show` will only show the closest local scope, so it won't show a shadowed function-level variable. Again, this is how local variables already work, and could be done as a separate change. As a fun tidbit, functions with --no-scope-shadowing can now use this to set variables in the calling function. That's probably okay given that it's already an escape hatch (but to be clear: if it turns out to problematic I reserve the right to remove it). Fixes #565
This commit is contained in:
parent
66709571ed
commit
733114fefb
|
@ -33,9 +33,11 @@ With ``--show``, ``set`` will describe the given variable names, explaining how
|
|||
|
||||
The following options control variable scope:
|
||||
|
||||
- ``-l`` or ``--local`` forces the specified shell variable to be given a scope that is local to the current block, even if a variable with the given name exists and is non-local
|
||||
- ``-f`` or ``--function`` scopes the variable to the currently executing function. It is erased when the function ends.
|
||||
|
||||
- ``-g`` or ``--global`` causes the specified shell variable to be given a global scope. Non-global variables disappear when the block they belong to ends
|
||||
- ``-l`` or ``--local`` scopes the variable to the currently executing block. It is erased when the block ends. Outside of a block, this is the same as ``--function``.
|
||||
|
||||
- ``-g`` or ``--global`` causes the specified shell variable to be given a global scope. Global variables don't disappear and are available to all functions running in the same shell. They can even be modified.
|
||||
|
||||
- ``-U`` or ``--universal`` causes the specified shell variable to be given a universal scope. If this option is supplied, the variable will be shared between all the current user's fish instances on the current computer, and will be preserved across restarts of the shell.
|
||||
|
||||
|
|
|
@ -878,19 +878,20 @@ So you set a variable with ``set``, and use it with a ``$`` and the name.
|
|||
Variable scope
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
There are three kinds of variables in fish: universal, global and local variables.
|
||||
There are four kinds of variables in fish: universal, global, function and local variables.
|
||||
|
||||
- Universal variables are shared between all fish sessions a user is running on one computer.
|
||||
- Global variables are specific to the current fish session, and will never be erased unless explicitly requested by using ``set -e``.
|
||||
- Local variables are specific to the current fish session, and associated with a specific block of commands, and automatically erased when a specific block goes out of scope. A block of commands is a series of commands that begins with one of the commands ``for``, ``while`` , ``if``, ``function``, ``begin`` or ``switch``, and ends with the command ``end``.
|
||||
- Function variables are specific to the currently executing function. They are erased ("go out of scope") when the current function ends.
|
||||
- Local variables are specific to the current block of commands, and automatically erased when a specific block goes out of scope. A block of commands is a series of commands that begins with one of the commands ``for``, ``while`` , ``if``, ``function``, ``begin`` or ``switch``, and ends with the command ``end``. Outside of a block, this is the same as the function scope.
|
||||
|
||||
Variables can be explicitly set to be universal with the ``-U`` or ``--universal`` switch, global with the ``-g`` or ``--global`` switch, or local with the ``-l`` or ``--local`` switch. The scoping rules when creating or updating a variable are:
|
||||
Variables can be explicitly set to be universal with the ``-U`` or ``--universal`` switch, global with ``-g`` or ``--global``, function-scoped with ``-f`` or ``--function`` and local to the current block with ``-l`` or ``--local``. The scoping rules when creating or updating a variable are:
|
||||
|
||||
- When a scope is explicitly given, it will be used. If a variable of the same name exists in a different scope, that variable will not be changed.
|
||||
|
||||
- When no scope is given, but a variable of that name exists, the variable of the smallest scope will be modified. The scope will not be changed.
|
||||
|
||||
- As a special case, when no scope is given and no variable has been defined the variable will belong to the scope of the currently executing *function*. This is different from the ``--local`` flag, which would make the variable local to the current *block*.
|
||||
- When no scope is given and no variable of that name exists, the variable is created in function scope if inside a function, or global scope if no function is executing.
|
||||
|
||||
There can be many variables with the same name, but different scopes. When you :ref:`use a variable <expand-variable>`, the smallest scoped variable of that name will be used. If a local variable exists, it will be used instead of the global or universal variable of the same name.
|
||||
|
||||
|
@ -908,6 +909,16 @@ Typically inside funcions you should use local scope::
|
|||
end
|
||||
end
|
||||
|
||||
# or
|
||||
|
||||
function something
|
||||
if test -e /path/to/my/file
|
||||
set -f file /path/to/my/file
|
||||
else
|
||||
set -f file /path/to/my/otherfile
|
||||
end
|
||||
end
|
||||
|
||||
If you want to set something in config.fish, or set something in a function and have it available for the rest of the session, global scope is a good choice::
|
||||
|
||||
# Don't shorten the working directory in the prompt
|
||||
|
@ -930,16 +941,24 @@ If you want to set some personal customization, universal variables are nice::
|
|||
|
||||
Here is an example of local vs function-scoped variables::
|
||||
|
||||
function test-scopes
|
||||
begin
|
||||
# This is a nice local scope where all variables will die
|
||||
set -l pirate 'There be treasure in them thar hills'
|
||||
set captain Space, the final frontier
|
||||
set -f captain Space, the final frontier
|
||||
# If no variable of that name was defined, it is function-local.
|
||||
set gnu "In the beginning there was nothing, which exploded"
|
||||
end
|
||||
|
||||
echo $pirate
|
||||
# This will not output anything, since the pirate was local
|
||||
echo $captain
|
||||
# This will output the good Captain's speech since $captain had function-scope.
|
||||
echo $gnu
|
||||
# Will output Sir Terry's wisdom.
|
||||
end
|
||||
|
||||
When in doubt, use function-scoped variables. When you need to make a variable accessible everywhere, make it global. When you need to persistently store configuration, make it universal. When you want to use a variable only in a short block, make it local.
|
||||
|
||||
.. _variables-override:
|
||||
|
||||
|
@ -1029,7 +1048,7 @@ Variables can be explicitly set to be exported with the ``-x`` or ``--export`` s
|
|||
|
||||
- Otherwise, by default, the variable will not be exported.
|
||||
|
||||
- If a variable has local scope and is exported, any function called receives a *copy* of it, so any changes it makes to the variable disappear once the function returns.
|
||||
- If a variable has function or local scope and is exported, any function called receives a *copy* of it, so any changes it makes to the variable disappear once the function returns.
|
||||
|
||||
- Global variables are accessible to functions whether they are exported or not.
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ struct set_cmd_opts_t {
|
|||
bool print_help = false;
|
||||
bool show = false;
|
||||
bool local = false;
|
||||
bool function = false;
|
||||
bool global = false;
|
||||
bool exportv = false;
|
||||
bool erase = false;
|
||||
|
@ -57,9 +58,10 @@ enum {
|
|||
// Variables used for parsing the argument list. This command is atypical in using the "+"
|
||||
// (REQUIRE_ORDER) option for flag parsing. This is not typical of most fish commands. It means
|
||||
// we stop scanning for flags when the first non-flag argument is seen.
|
||||
static const wchar_t *const short_options = L"+:LSUaeghlnpqux";
|
||||
static const wchar_t *const short_options = L"+:LSUaefghlnpqux";
|
||||
static const struct woption long_options[] = {
|
||||
{L"export", no_argument, nullptr, 'x'}, {L"global", no_argument, nullptr, 'g'},
|
||||
{L"function", no_argument, nullptr, 'f'},
|
||||
{L"local", no_argument, nullptr, 'l'}, {L"erase", no_argument, nullptr, 'e'},
|
||||
{L"names", no_argument, nullptr, 'n'}, {L"unexport", no_argument, nullptr, 'u'},
|
||||
{L"universal", no_argument, nullptr, 'U'}, {L"long", no_argument, nullptr, 'L'},
|
||||
|
@ -94,6 +96,10 @@ static int parse_cmd_opts(set_cmd_opts_t &opts, int *optind, //!OCLINT(high ncs
|
|||
opts.preserve_failure_exit_status = false;
|
||||
break;
|
||||
}
|
||||
case 'f': {
|
||||
opts.function = true;
|
||||
break;
|
||||
}
|
||||
case 'g': {
|
||||
opts.global = true;
|
||||
break;
|
||||
|
@ -185,7 +191,7 @@ static int validate_cmd_opts(const wchar_t *cmd,
|
|||
}
|
||||
|
||||
// Variables can only have one scope.
|
||||
if (opts.local + opts.global + opts.universal > 1) {
|
||||
if (opts.local + opts.function + opts.global + opts.universal > 1) {
|
||||
streams.err.append_format(BUILTIN_ERR_GLOCAL, cmd);
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
return STATUS_INVALID_ARGS;
|
||||
|
@ -214,7 +220,7 @@ static int validate_cmd_opts(const wchar_t *cmd,
|
|||
|
||||
// The --show flag cannot be combined with any other flag.
|
||||
if (opts.show &&
|
||||
(opts.local || opts.global || opts.erase || opts.list || opts.exportv || opts.universal)) {
|
||||
(opts.local || opts.function || opts.global || opts.erase || opts.list || opts.exportv || opts.universal)) {
|
||||
streams.err.append_format(BUILTIN_ERR_COMBO, cmd);
|
||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||
return STATUS_INVALID_ARGS;
|
||||
|
@ -393,6 +399,7 @@ static wcstring_list_t erased_at_indexes(wcstring_list_t input, std::vector<long
|
|||
static env_mode_flags_t compute_scope(const set_cmd_opts_t &opts) {
|
||||
int scope = ENV_USER;
|
||||
if (opts.local) scope |= ENV_LOCAL;
|
||||
if (opts.function) scope |= ENV_FUNCTION;
|
||||
if (opts.global) scope |= ENV_GLOBAL;
|
||||
if (opts.exportv) scope |= ENV_EXPORT;
|
||||
if (opts.unexport) scope |= ENV_UNEXPORT;
|
||||
|
|
22
src/env.cpp
22
src/env.cpp
|
@ -472,8 +472,9 @@ struct query_t {
|
|||
// Whether any scopes were specified.
|
||||
bool has_scope;
|
||||
|
||||
// Whether to search local, global, universal scopes.
|
||||
// Whether to search local, function, global, universal scopes.
|
||||
bool local;
|
||||
bool function;
|
||||
bool global;
|
||||
bool universal;
|
||||
|
||||
|
@ -493,8 +494,9 @@ struct query_t {
|
|||
bool user;
|
||||
|
||||
explicit query_t(env_mode_flags_t mode) {
|
||||
has_scope = mode & (ENV_LOCAL | ENV_GLOBAL | ENV_UNIVERSAL);
|
||||
has_scope = mode & (ENV_LOCAL | ENV_FUNCTION | ENV_GLOBAL | ENV_UNIVERSAL);
|
||||
local = !has_scope || (mode & ENV_LOCAL);
|
||||
function = !has_scope || (mode & ENV_FUNCTION);
|
||||
global = !has_scope || (mode & ENV_GLOBAL);
|
||||
universal = !has_scope || (mode & ENV_UNIVERSAL);
|
||||
|
||||
|
@ -1190,6 +1192,22 @@ mod_result_t env_stack_impl_t::set(const wcstring &key, env_mode_flags_t mode,
|
|||
} else if (query.local) {
|
||||
assert(locals_ != globals_ && "Locals should not be globals");
|
||||
set_in_node(locals_, key, std::move(val), flags);
|
||||
} else if (query.function) {
|
||||
// "Function" scope is:
|
||||
// Either the topmost local scope of the nearest function,
|
||||
// or the top-level local scope if no function exists.
|
||||
//
|
||||
// This is distinct from the unspecified scope,
|
||||
// which is the global scope if no function exists.
|
||||
auto node = locals_;
|
||||
while (node->next) {
|
||||
node = node->next;
|
||||
// The first node that introduces a new scope is ours.
|
||||
// If this doesn't happen, we go on until we've reached the
|
||||
// topmost local scope.
|
||||
if (node->new_scope) break;
|
||||
}
|
||||
set_in_node(node, key, std::move(val), flags);
|
||||
} else {
|
||||
DIE("Unknown scope");
|
||||
}
|
||||
|
|
15
src/env.h
15
src/env.h
|
@ -26,23 +26,24 @@ enum {
|
|||
ENV_DEFAULT = 0,
|
||||
/// Flag for local (to the current block) variable.
|
||||
ENV_LOCAL = 1 << 0,
|
||||
ENV_FUNCTION = 1 << 1,
|
||||
/// Flag for global variable.
|
||||
ENV_GLOBAL = 1 << 1,
|
||||
ENV_GLOBAL = 1 << 2,
|
||||
/// Flag for universal variable.
|
||||
ENV_UNIVERSAL = 1 << 2,
|
||||
ENV_UNIVERSAL = 1 << 3,
|
||||
/// Flag for exported (to commands) variable.
|
||||
ENV_EXPORT = 1 << 3,
|
||||
ENV_EXPORT = 1 << 4,
|
||||
/// Flag for unexported variable.
|
||||
ENV_UNEXPORT = 1 << 4,
|
||||
ENV_UNEXPORT = 1 << 5,
|
||||
/// Flag to mark a variable as a path variable.
|
||||
ENV_PATHVAR = 1 << 5,
|
||||
ENV_PATHVAR = 1 << 6,
|
||||
/// Flag to unmark a variable as a path variable.
|
||||
ENV_UNPATHVAR = 1 << 6,
|
||||
ENV_UNPATHVAR = 1 << 7,
|
||||
/// Flag for variable update request from the user. All variable changes that are made directly
|
||||
/// by the user, such as those from the `read` and `set` builtin must have this flag set. It
|
||||
/// serves one purpose: to indicate that an error should be returned if the user is attempting
|
||||
/// to modify a var that should not be modified by direct user action; e.g., a read-only var.
|
||||
ENV_USER = 1 << 7,
|
||||
ENV_USER = 1 << 8,
|
||||
};
|
||||
typedef uint32_t env_mode_flags_t;
|
||||
|
||||
|
|
|
@ -732,3 +732,77 @@ begin
|
|||
echo $CDPATH
|
||||
# CHECK: . /usr
|
||||
end
|
||||
|
||||
# Function scope:
|
||||
set -f actuallystilllocal "this one is still local"
|
||||
set -ql actuallystilllocal
|
||||
and echo "Yep, it's local"
|
||||
# CHECK: Yep, it's local
|
||||
set -S actuallystilllocal
|
||||
#CHECK: $actuallystilllocal: set in local scope, unexported, with 1 elements
|
||||
#CHECK: $actuallystilllocal[1]: |this one is still local|
|
||||
|
||||
# Blocks aren't functions, "function" scope is still top-level local:
|
||||
begin
|
||||
set -f stilllocal "as local as the moon is wet"
|
||||
echo $stilllocal
|
||||
# CHECK: as local as the moon is wet
|
||||
end
|
||||
set -S stilllocal
|
||||
#CHECK: $stilllocal: set in local scope, unexported, with 1 elements
|
||||
#CHECK: $stilllocal[1]: |as local as the moon is wet|
|
||||
|
||||
set -g globalvar global
|
||||
|
||||
function test-function-scope
|
||||
set -f funcvar "function"
|
||||
echo $funcvar
|
||||
# CHECK: function
|
||||
set -S funcvar
|
||||
#CHECK: $funcvar: set in local scope, unexported, with 1 elements
|
||||
#CHECK: $funcvar[1]: |function|
|
||||
begin
|
||||
set -l funcvar "block"
|
||||
echo $funcvar
|
||||
# CHECK: block
|
||||
set -S funcvar
|
||||
#CHECK: $funcvar: set in local scope, unexported, with 1 elements
|
||||
#CHECK: $funcvar[1]: |block|
|
||||
end
|
||||
echo $funcvar
|
||||
# CHECK: function
|
||||
|
||||
begin
|
||||
set -f funcvar2 "function from block"
|
||||
echo $funcvar2
|
||||
# CHECK: function from block
|
||||
set -S funcvar2
|
||||
#CHECK: $funcvar2: set in local scope, unexported, with 1 elements
|
||||
#CHECK: $funcvar2[1]: |function from block|
|
||||
end
|
||||
echo $funcvar2
|
||||
# CHECK: function from block
|
||||
set -S funcvar2
|
||||
#CHECK: $funcvar2: set in local scope, unexported, with 1 elements
|
||||
#CHECK: $funcvar2[1]: |function from block|
|
||||
|
||||
set -l fruit banana
|
||||
if true
|
||||
set -f fruit orange
|
||||
end
|
||||
echo $fruit #orange
|
||||
# function scope *is* the outermost local scope,
|
||||
# so that `set -f` altered the same funcvariable as that `set -l` outside!
|
||||
# CHECK: orange
|
||||
|
||||
set -f globalvar function
|
||||
set -S globalvar
|
||||
#CHECK: $globalvar: set in local scope, unexported, with 1 elements
|
||||
#CHECK: $globalvar[1]: |function|
|
||||
#CHECK: $globalvar: set in global scope, unexported, with 1 elements
|
||||
#CHECK: $globalvar[1]: |global|
|
||||
end
|
||||
|
||||
test-function-scope
|
||||
echo $funcvar $funcvar2
|
||||
# CHECK:
|
||||
|
|
Loading…
Reference in New Issue
Block a user