From a672edc0d5e5161e8a468b8ac0c4346db792f0b0 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sun, 14 May 2023 11:40:18 -0700 Subject: [PATCH] Adopt the new function store and rewrite builtin_function This adopts the new function store, replacing the C++ version. It also reimplements builtin_function in Rust, as these was too coupled to the function store to handle in a separate commit. --- CMakeLists.txt | 2 +- fish-rust/build.rs | 2 + fish-rust/src/ast.rs | 5 + fish-rust/src/builtins/function.rs | 430 ++++++++++++++++++++++++++++ fish-rust/src/builtins/mod.rs | 1 + fish-rust/src/builtins/shared.rs | 33 ++- fish-rust/src/builtins/type.rs | 33 +-- fish-rust/src/env/env_ffi.rs | 15 + fish-rust/src/env_dispatch.rs | 3 +- fish-rust/src/event.rs | 2 +- fish-rust/src/ffi.rs | 14 +- fish-rust/src/function.rs | 1 + fish-rust/src/wait_handle.rs | 5 + src/ast.h | 4 +- src/builtins/function.cpp | 333 ---------------------- src/builtins/function.h | 14 - src/builtins/functions.cpp | 30 +- src/complete.cpp | 8 +- src/env.cpp | 4 + src/env.h | 4 + src/exec.cpp | 24 +- src/fish_tests.cpp | 22 +- src/function.cpp | 437 +---------------------------- src/function.h | 120 +------- src/parse_execution.cpp | 12 +- src/parser.cpp | 10 +- src/parser.h | 3 +- 27 files changed, 588 insertions(+), 983 deletions(-) create mode 100644 fish-rust/src/builtins/function.rs delete mode 100644 src/builtins/function.cpp delete mode 100644 src/builtins/function.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c689cba12..1e2112f38 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,7 +103,7 @@ set(FISH_BUILTIN_SRCS src/builtins/commandline.cpp src/builtins/complete.cpp src/builtins/disown.cpp src/builtins/eval.cpp src/builtins/fg.cpp - src/builtins/function.cpp src/builtins/functions.cpp src/builtins/history.cpp + src/builtins/functions.cpp src/builtins/history.cpp src/builtins/jobs.cpp src/builtins/path.cpp src/builtins/read.cpp src/builtins/set.cpp src/builtins/source.cpp diff --git a/fish-rust/build.rs b/fish-rust/build.rs index d0153f1ef..07b1d31d5 100644 --- a/fish-rust/build.rs +++ b/fish-rust/build.rs @@ -41,6 +41,7 @@ fn main() { "src/abbrs.rs", "src/ast.rs", "src/builtins/shared.rs", + "src/builtins/function.rs", "src/common.rs", "src/env/env_ffi.rs", "src/env_dispatch.rs", @@ -51,6 +52,7 @@ fn main() { "src/ffi_init.rs", "src/ffi_tests.rs", "src/fish_indent.rs", + "src/function.rs", "src/future_feature_flags.rs", "src/highlight.rs", "src/job_group.rs", diff --git a/fish-rust/src/ast.rs b/fish-rust/src/ast.rs index e1a00f3f4..f80e34961 100644 --- a/fish-rust/src/ast.rs +++ b/fish-rust/src/ast.rs @@ -4469,6 +4469,11 @@ impl Statement { } } +unsafe impl ExternType for BlockStatement { + type Id = type_id!("BlockStatement"); + type Kind = cxx::kind::Opaque; +} + #[derive(Clone)] pub enum NodeFfi<'a> { None, diff --git a/fish-rust/src/builtins/function.rs b/fish-rust/src/builtins/function.rs new file mode 100644 index 000000000..5d3ff4f51 --- /dev/null +++ b/fish-rust/src/builtins/function.rs @@ -0,0 +1,430 @@ +use super::shared::{ + builtin_missing_argument, builtin_print_error_trailer, builtin_unknown_option, io_streams_t, + truncate_args_on_nul, BUILTIN_ERR_VARNAME, STATUS_INVALID_ARGS, +}; +use crate::ast::BlockStatement; +use crate::builtins::shared::STATUS_CMD_OK; +use crate::common::{valid_func_name, valid_var_name}; +use crate::env::environment::Environment; +use crate::event::{self, EventDescription, EventHandler}; +use crate::ffi::{self, io_streams_t as io_streams_ffi_t, parser_t, Repin}; +use crate::function; +use crate::global_safety::RelaxedAtomicBool; +use crate::parse_tree::NodeRef; +use crate::parse_tree::ParsedSourceRefFFI; +use crate::parser_keywords::parser_keywords_is_reserved; +use crate::signal::Signal; +use crate::wchar::{wstr, WString, L}; +use crate::wchar_ffi::{wcstring_list_ffi_t, WCharFromFFI, WCharToFFI}; +use crate::wgetopt::{wgetopter_t, wopt, woption, woption_argument_t, NONOPTION_CHAR_CODE}; +use crate::wutil::{fish_wcstoi, wgettext_fmt}; +use libc::c_int; +use std::pin::Pin; +use std::sync::Arc; + +struct FunctionCmdOpts { + print_help: bool, + shadow_scope: bool, + description: WString, + events: Vec, + named_arguments: Vec, + inherit_vars: Vec, + wrap_targets: Vec, +} + +impl Default for FunctionCmdOpts { + fn default() -> Self { + Self { + print_help: false, + shadow_scope: true, + description: WString::new(), + events: Vec::new(), + named_arguments: Vec::new(), + inherit_vars: Vec::new(), + wrap_targets: Vec::new(), + } + } +} + +// This command is atypical in using the "-" (RETURN_IN_ORDER) option for flag parsing. +// This is needed due to the semantics of the -a/--argument-names flag. +const SHORT_OPTIONS: &wstr = L!("-:a:d:e:hj:p:s:v:w:SV:"); +#[rustfmt::skip] +const LONG_OPTIONS: &[woption] = &[ + wopt(L!("description"), woption_argument_t::required_argument, 'd'), + wopt(L!("on-signal"), woption_argument_t::required_argument, 's'), + wopt(L!("on-job-exit"), woption_argument_t::required_argument, 'j'), + wopt(L!("on-process-exit"), woption_argument_t::required_argument, 'p'), + wopt(L!("on-variable"), woption_argument_t::required_argument, 'v'), + wopt(L!("on-event"), woption_argument_t::required_argument, 'e'), + wopt(L!("wraps"), woption_argument_t::required_argument, 'w'), + wopt(L!("help"), woption_argument_t::no_argument, 'h'), + wopt(L!("argument-names"), woption_argument_t::required_argument, 'a'), + wopt(L!("no-scope-shadowing"), woption_argument_t::no_argument, 'S'), + wopt(L!("inherit-variable"), woption_argument_t::required_argument, 'V'), +]; + +/// \return the internal_job_id for a pid, or None if none. +/// This looks through both active and finished jobs. +fn job_id_for_pid(pid: i32, parser: &parser_t) -> Option { + if let Some(job) = parser.job_get_from_pid(pid) { + Some(job.get_internal_job_id()) + } else { + parser + .get_wait_handles() + .get_by_pid(pid) + .map(|h| h.internal_job_id) + } +} + +/// Parses options to builtin function, populating opts. +/// Returns an exit status. +fn parse_cmd_opts( + opts: &mut FunctionCmdOpts, + optind: &mut usize, + argv: &mut [&wstr], + parser: &mut parser_t, + streams: &mut io_streams_t, +) -> Option { + let cmd = L!("function"); + let print_hints = false; + let mut handling_named_arguments = false; + let mut w = wgetopter_t::new(SHORT_OPTIONS, LONG_OPTIONS, argv); + while let Some(opt) = w.wgetopt_long() { + // NONOPTION_CHAR_CODE is returned when we reach a non-permuted non-option. + if opt != 'a' && opt != NONOPTION_CHAR_CODE { + handling_named_arguments = false; + } + match opt { + NONOPTION_CHAR_CODE => { + // A positional argument we got because we use RETURN_IN_ORDER. + let woptarg = w.woptarg.unwrap().to_owned(); + if handling_named_arguments { + opts.named_arguments.push(woptarg); + } else { + streams.err.append(wgettext_fmt!( + "%ls: %ls: unexpected positional argument", + cmd, + woptarg + )); + return STATUS_INVALID_ARGS; + } + } + 'd' => { + opts.description = w.woptarg.unwrap().to_owned(); + } + 's' => { + let Some(signal) = Signal::parse(w.woptarg.unwrap()) else { + streams.err.append(wgettext_fmt!("%ls: Unknown signal '%ls'", cmd, w.woptarg.unwrap())); + return STATUS_INVALID_ARGS; + }; + opts.events.push(EventDescription::Signal { signal }); + } + 'v' => { + let name = w.woptarg.unwrap().to_owned(); + if !valid_var_name(&name) { + streams + .err + .append(wgettext_fmt!(BUILTIN_ERR_VARNAME, cmd, name)); + return STATUS_INVALID_ARGS; + } + opts.events.push(EventDescription::Variable { name }); + } + 'e' => { + let param = w.woptarg.unwrap().to_owned(); + opts.events.push(EventDescription::Generic { param }); + } + 'j' | 'p' => { + let woptarg = w.woptarg.unwrap(); + let e: EventDescription; + if opt == 'j' && woptarg == "caller" { + let libdata = parser.ffi_libdata_pod_const(); + let caller_id = if libdata.is_subshell { + libdata.caller_id + } else { + 0 + }; + if caller_id == 0 { + streams.err.append(wgettext_fmt!( + "%ls: calling job for event handler not found", + cmd + )); + return STATUS_INVALID_ARGS; + } + e = EventDescription::CallerExit { caller_id }; + } else if opt == 'p' && woptarg == "%self" { + // Safety: getpid() is always successful. + let pid = unsafe { libc::getpid() }; + e = EventDescription::ProcessExit { pid }; + } else { + let pid = fish_wcstoi(woptarg); + if pid.is_err() || pid.unwrap() < 0 { + streams + .err + .append(wgettext_fmt!("%ls: %ls: invalid process id", cmd)); + return STATUS_INVALID_ARGS; + } + let pid = pid.unwrap(); + if opt == 'p' { + e = EventDescription::ProcessExit { pid }; + } else { + // TODO: rationalize why a default of 0 is sensible. + let internal_job_id = job_id_for_pid(pid, parser).unwrap_or(0); + e = EventDescription::JobExit { + pid, + internal_job_id, + }; + } + } + opts.events.push(e); + } + 'a' => { + handling_named_arguments = true; + opts.named_arguments.push(w.woptarg.unwrap().to_owned()); + } + 'S' => { + opts.shadow_scope = false; + } + 'w' => { + opts.wrap_targets.push(w.woptarg.unwrap().to_owned()); + } + 'V' => { + let woptarg = w.woptarg.unwrap(); + if !valid_var_name(woptarg) { + streams + .err + .append(wgettext_fmt!(BUILTIN_ERR_VARNAME, cmd, woptarg)); + return STATUS_INVALID_ARGS; + } + opts.inherit_vars.push(woptarg.to_owned()); + } + 'h' => { + opts.print_help = true; + } + ':' => { + builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1], print_hints); + return STATUS_INVALID_ARGS; + } + '?' => { + builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1], print_hints); + return STATUS_INVALID_ARGS; + } + other => { + panic!("Unexpected retval from wgetopt_long: {}", other); + } + } + } + + *optind = w.woptind; + STATUS_CMD_OK +} + +fn validate_function_name( + argv: &mut [&wstr], + function_name: &mut WString, + cmd: &wstr, + streams: &mut io_streams_t, +) -> Option { + if argv.len() < 2 { + // This is currently impossible but let's be paranoid. + streams + .err + .append(wgettext_fmt!("%ls: function name required", cmd)); + return STATUS_INVALID_ARGS; + } + *function_name = argv[1].to_owned(); + if !valid_func_name(function_name) { + streams.err.append(wgettext_fmt!( + "%ls: %ls: invalid function name", + cmd, + function_name, + )); + return STATUS_INVALID_ARGS; + } + if parser_keywords_is_reserved(function_name) { + streams.err.append(wgettext_fmt!( + "%ls: %ls: cannot use reserved keyword as function name", + cmd, + function_name + )); + return STATUS_INVALID_ARGS; + } + STATUS_CMD_OK +} + +/// Define a function. Calls into `function.rs` to perform the heavy lifting of defining a +/// function. Note this isn't strictly a "builtin": it is called directly from parse_execution. +/// That is why its signature is different from the other builtins. +pub fn function( + parser: &mut parser_t, + streams: &mut io_streams_t, + c_args: &mut [&wstr], + func_node: NodeRef, +) -> Option { + // The wgetopt function expects 'function' as the first argument. Make a new vec with + // that property. This is needed because this builtin has a different signature than the other + // builtins. + let mut args = vec![L!("function")]; + args.extend_from_slice(c_args); + let argv: &mut [&wstr] = &mut args; + let cmd = argv[0]; + + // A valid function name has to be the first argument. + let mut function_name = WString::new(); + let mut retval = validate_function_name(argv, &mut function_name, cmd, streams); + if retval != STATUS_CMD_OK { + return retval; + } + let argv = &mut argv[1..]; + + let mut opts = FunctionCmdOpts::default(); + let mut optind = 0; + retval = parse_cmd_opts(&mut opts, &mut optind, argv, parser, streams); + if retval != STATUS_CMD_OK { + return retval; + } + + if opts.print_help { + builtin_print_error_trailer(parser, streams, cmd); + return STATUS_CMD_OK; + } + + if argv.len() != optind { + if !opts.named_arguments.is_empty() { + // Remaining arguments are named arguments. + for &arg in argv[optind..].iter() { + if !valid_var_name(arg) { + streams + .err + .append(wgettext_fmt!(BUILTIN_ERR_VARNAME, cmd, arg)); + return STATUS_INVALID_ARGS; + } + opts.named_arguments.push(arg.to_owned()); + } + } else { + streams.err.append(wgettext_fmt!( + "%ls: %ls: unexpected positional argument", + cmd, + argv[optind], + )); + return STATUS_INVALID_ARGS; + } + } + + // Extract the current filename. + let definition_file = unsafe { parser.pin().libdata().get_current_filename().as_ref() } + .map(|s| Arc::new(s.from_ffi())); + + // We have what we need to actually define the function. + let mut props = function::FunctionProperties { + func_node, + named_arguments: opts.named_arguments, + description: opts.description, + inherit_vars: Default::default(), + shadow_scope: opts.shadow_scope, + is_autoload: RelaxedAtomicBool::new(false), + definition_file, + is_copy: false, + copy_definition_file: None, + copy_definition_lineno: 0, + }; + + // Populate inherit_vars. + for name in opts.inherit_vars.into_iter() { + if let Some(var) = parser.get_vars().get(&name) { + props.inherit_vars.insert(name, var.as_list().to_vec()); + } + } + + // Add the function itself. + function::add(function_name.clone(), Arc::new(props)); + + // Handle wrap targets by creating the appropriate completions. + for wt in opts.wrap_targets.into_iter() { + ffi::complete_add_wrapper(&function_name.to_ffi(), &wt.to_ffi()); + } + + // Add any event handlers. + for ed in &opts.events { + event::add_handler(EventHandler::new(ed.clone(), Some(function_name.clone()))); + } + + // If there is an --on-process-exit or --on-job-exit event handler for some pid, and that + // process has already exited, run it immediately (#7210). + for ed in &opts.events { + match *ed { + EventDescription::ProcessExit { pid } if pid != event::ANY_PID => { + if let Some(status) = parser + .get_wait_handles() + .get_by_pid(pid) + .and_then(|wh| wh.status()) + { + event::fire(parser, event::Event::process_exit(pid, status)); + } + } + EventDescription::JobExit { pid, .. } if pid != event::ANY_PID => { + if let Some(wh) = parser.get_wait_handles().get_by_pid(pid) { + if wh.is_completed() { + event::fire(parser, event::Event::job_exit(pid, wh.internal_job_id)); + } + } + } + _ => (), + } + } + + STATUS_CMD_OK +} + +fn builtin_function_ffi( + parser: Pin<&mut parser_t>, + streams: Pin<&mut io_streams_ffi_t>, + c_args: &wcstring_list_ffi_t, + source_u8: *const u8, // unowned ParsedSourceRefFFI + func_node: &BlockStatement, +) -> i32 { + let storage = c_args.from_ffi(); + let mut args = truncate_args_on_nul(&storage); + let node = unsafe { + let source_ref: &ParsedSourceRefFFI = &*(source_u8.cast()); + NodeRef::from_parts( + source_ref + .0 + .as_ref() + .expect("Should have parsed source") + .clone(), + func_node, + ) + }; + function( + parser.unpin(), + &mut io_streams_t::new(streams), + args.as_mut_slice(), + node, + ) + .expect("function builtin should always return a non-None status") +} + +#[cxx::bridge] +mod builtin_function { + extern "C++" { + include!("ast.h"); + include!("parser.h"); + include!("io.h"); + type parser_t = crate::ffi::parser_t; + type io_streams_t = crate::ffi::io_streams_t; + type wcstring_list_ffi_t = crate::ffi::wcstring_list_ffi_t; + + type BlockStatement = crate::ast::BlockStatement; + } + + extern "Rust" { + fn builtin_function_ffi( + parser: Pin<&mut parser_t>, + streams: Pin<&mut io_streams_t>, + c_args: &wcstring_list_ffi_t, + source: *const u8, // unowned ParsedSourceRefFFI + func_node: &BlockStatement, + ) -> i32; + } +} diff --git a/fish-rust/src/builtins/mod.rs b/fish-rust/src/builtins/mod.rs index 17ec35d72..4ae2432be 100644 --- a/fish-rust/src/builtins/mod.rs +++ b/fish-rust/src/builtins/mod.rs @@ -11,6 +11,7 @@ pub mod contains; pub mod echo; pub mod emit; pub mod exit; +pub mod function; pub mod math; pub mod printf; pub mod pwd; diff --git a/fish-rust/src/builtins/shared.rs b/fish-rust/src/builtins/shared.rs index 570193fd8..11559e3da 100644 --- a/fish-rust/src/builtins/shared.rs +++ b/fish-rust/src/builtins/shared.rs @@ -3,6 +3,7 @@ use crate::ffi::{self, parser_t, wcstring_list_ffi_t, Repin, RustBuiltin}; use crate::wchar::{wstr, WString, L}; use crate::wchar_ffi::{c_str, empty_wstring, ToCppWString, WCharFromFFI}; use crate::wgetopt::{wgetopter_t, wopt, woption, woption_argument_t}; +use cxx::{type_id, ExternType}; use libc::c_int; use std::os::fd::RawFd; use std::pin::Pin; @@ -31,6 +32,11 @@ mod builtins_ffi { } } +unsafe impl ExternType for io_streams_t { + type Id = type_id!("io_streams_t"); + type Kind = cxx::kind::Opaque; +} + /// Error message when too many arguments are supplied to a builtin. pub const BUILTIN_ERR_TOO_MANY_ARGUMENTS: &str = "%ls: too many arguments\n"; @@ -50,6 +56,9 @@ pub const BUILTIN_ERR_ARG_COUNT2: &str = "%ls: %ls: expected %d arguments; got % pub const BUILTIN_ERR_MIN_ARG_COUNT1: &str = "%ls: expected >= %d arguments; got %d\n"; pub const BUILTIN_ERR_MAX_ARG_COUNT1: &str = "%ls: expected <= %d arguments; got %d\n"; +/// Error message for invalid variable name. +pub const BUILTIN_ERR_VARNAME: &str = "%ls: %ls: invalid variable name. See `help identifiers`\n"; + /// Error message on invalid combination of options. pub const BUILTIN_ERR_COMBO: &str = "%ls: invalid option combination\n"; pub const BUILTIN_ERR_COMBO2: &str = "%ls: invalid option combination, %ls\n"; @@ -149,6 +158,19 @@ impl io_streams_t { } } +/// Helper function to convert Vec to Vec<&wstr>, truncating on NUL. +/// We truncate on NUL for backwards-compatibility reasons. +/// This used to be passed as c-strings (`wchar_t *`), +/// and so we act like it for now. +pub fn truncate_args_on_nul(args: &[WString]) -> Vec<&wstr> { + args.iter() + .map(|s| match s.chars().position(|c| c == '\0') { + Some(i) => &s[..i], + None => &s[..], + }) + .collect() +} + fn rust_run_builtin( parser: Pin<&mut parser_t>, streams: Pin<&mut builtins_ffi::io_streams_t>, @@ -157,16 +179,7 @@ fn rust_run_builtin( status_code: &mut i32, ) -> bool { let storage: Vec = cpp_args.from_ffi(); - let mut args: Vec<&wstr> = storage - .iter() - .map(|s| match s.chars().position(|c| c == '\0') { - // We truncate on NUL for backwards-compatibility reasons. - // This used to be passed as c-strings (`wchar_t *`), - // and so we act like it for now. - Some(pos) => &s[..pos], - None => &s[..], - }) - .collect(); + let mut args: Vec<&wstr> = truncate_args_on_nul(&storage); let streams = &mut io_streams_t::new(streams); match run_builtin(parser.unpin(), streams, args.as_mut_slice(), builtin) { diff --git a/fish-rust/src/builtins/type.rs b/fish-rust/src/builtins/type.rs index 7cc392d8b..26e1560be 100644 --- a/fish-rust/src/builtins/type.rs +++ b/fish-rust/src/builtins/type.rs @@ -8,12 +8,8 @@ use crate::builtins::shared::{ }; use crate::ffi::parser_t; use crate::ffi::Repin; -use crate::ffi::{ - builtin_exists, colorize_shell, function_get_annotated_definition, - function_get_copy_definition_file, function_get_copy_definition_lineno, - function_get_definition_file, function_get_definition_lineno, function_get_props_autoload, - function_is_copy, -}; +use crate::ffi::{builtin_exists, colorize_shell}; +use crate::function; use crate::path::{path_get_path, path_get_paths}; use crate::wchar::{wstr, WString, L}; use crate::wchar_ffi::WCharFromFFI; @@ -95,8 +91,7 @@ pub fn r#type( for arg in argv.iter().take(argc).skip(optind) { let mut found = 0; if !opts.force_path && !opts.no_functions { - let props = function_get_props_autoload(&arg.to_ffi(), parser.pin()); - if !props.is_null() { + if let Some(props) = function::get_props_autoload(arg, parser) { found += 1; res = true; // Early out - query means *any of the args exists*. @@ -104,7 +99,7 @@ pub fn r#type( return STATUS_CMD_OK; } if !opts.get_type { - let path = function_get_definition_file(&props).from_ffi(); + let path = props.definition_file().unwrap_or(L!("")); let mut comment = WString::new(); if path.is_empty() { @@ -112,7 +107,7 @@ pub fn r#type( } else if path == "-" { comment.push_utfstr(&wgettext_fmt!("Defined via `source`")); } else { - let lineno: i32 = i32::from(function_get_definition_lineno(&props)); + let lineno: i32 = props.definition_lineno(); comment.push_utfstr(&wgettext_fmt!( "Defined in %ls @ line %d", path, @@ -120,15 +115,14 @@ pub fn r#type( )); } - if function_is_copy(&props) { - let path = function_get_copy_definition_file(&props).from_ffi(); + if props.is_copy() { + let path = props.copy_definition_file().unwrap_or(L!("")); if path.is_empty() { comment.push_utfstr(&wgettext_fmt!(", copied interactively")); } else if path == "-" { comment.push_utfstr(&wgettext_fmt!(", copied via `source`")); } else { - let lineno: i32 = - i32::from(function_get_copy_definition_lineno(&props)); + let lineno: i32 = props.copy_definition_lineno(); comment.push_utfstr(&wgettext_fmt!( ", copied in %ls @ line %d", path, @@ -137,22 +131,21 @@ pub fn r#type( } } if opts.path { - if function_is_copy(&props) { - let path = function_get_copy_definition_file(&props).from_ffi(); - streams.out.append(path); + if let Some(orig_path) = props.copy_definition_file() { + streams.out.append(orig_path); } else { streams.out.append(path); } - streams.out.append(L!("\n")); + streams.out.append1('\n'); } else if !opts.short_output { streams.out.append(wgettext_fmt!("%ls is a function", arg)); streams.out.append(wgettext_fmt!(" with definition")); - streams.out.append(L!("\n")); + streams.out.append1('\n'); let mut def = WString::new(); def.push_utfstr(&sprintf!( "# %ls\n%ls", comment, - function_get_annotated_definition(&props, &arg.to_ffi()).from_ffi() + props.annotated_definition(arg) )); if !streams.out_is_redirected && unsafe { isatty(STDOUT_FILENO) == 1 } { diff --git a/fish-rust/src/env/env_ffi.rs b/fish-rust/src/env/env_ffi.rs index 9f68478cb..c40cd1de2 100644 --- a/fish-rust/src/env/env_ffi.rs +++ b/fish-rust/src/env/env_ffi.rs @@ -3,6 +3,7 @@ use super::var::{ElectricVar, EnvVar, EnvVarFlags, Statuses}; use crate::env::EnvMode; use crate::event::Event; use crate::ffi::{event_list_ffi_t, wchar_t, wcharz_t, wcstring_list_ffi_t}; +use crate::function::FunctionPropertiesRefFFI; use crate::null_terminated_array::OwningNullTerminatedArrayRefFFI; use crate::signal::Signal; use crate::wchar_ffi::WCharToFFI; @@ -31,6 +32,7 @@ mod env_ffi { type event_list_ffi_t = super::event_list_ffi_t; type wcstring_list_ffi_t = super::wcstring_list_ffi_t; type wcharz_t = super::wcharz_t; + type function_properties_t = super::FunctionPropertiesRefFFI; type OwningNullTerminatedArrayRefFFI = crate::null_terminated_array::OwningNullTerminatedArrayRefFFI; @@ -138,6 +140,8 @@ mod env_ffi { fn env_get_principal_ffi() -> Box; fn universal_sync(&self, always: bool, out_events: Pin<&mut event_list_ffi_t>); + + fn apply_inherited_ffi(&self, props: &function_properties_t); } extern "Rust" { @@ -296,6 +300,17 @@ impl EnvStackRefFFI { out_events.as_mut().push(Box::into_raw(event).cast()); } } + + fn apply_inherited_ffi(&self, props: &FunctionPropertiesRefFFI) { + // Ported from C++: + // for (const auto &kv : props.inherit_vars) { + // vars.set(kv.first, ENV_LOCAL | ENV_USER, kv.second); + // } + for (name, vals) in props.0.inherit_vars() { + self.0 + .set(name, EnvMode::LOCAL | EnvMode::USER, vals.clone()); + } + } } impl Statuses { diff --git a/fish-rust/src/env_dispatch.rs b/fish-rust/src/env_dispatch.rs index 17c3ccba7..61d6b655b 100644 --- a/fish-rust/src/env_dispatch.rs +++ b/fish-rust/src/env_dispatch.rs @@ -4,6 +4,7 @@ use crate::env::{setenv_lock, unsetenv_lock, EnvMode, EnvStack, Environment}; use crate::env::{CURSES_INITIALIZED, READ_BYTE_LIMIT, TERM_HAS_XN}; use crate::ffi::is_interactive_session; use crate::flog::FLOGF; +use crate::function; use crate::output::ColorSupport; use crate::wchar::L; use crate::wchar::{wstr, WString}; @@ -273,7 +274,7 @@ fn handle_autosuggestion_change(vars: &EnvStack) { } fn handle_function_path_change(_: &EnvStack) { - crate::ffi::function_invalidate_path(); + function::invalidate_path(); } fn handle_complete_path_change(_: &EnvStack) { diff --git a/fish-rust/src/event.rs b/fish-rust/src/event.rs index 4461be6bc..5db683ec5 100644 --- a/fish-rust/src/event.rs +++ b/fish-rust/src/event.rs @@ -102,7 +102,7 @@ mod event_ffi { pub use event_ffi::{event_description_t, event_type_t}; -const ANY_PID: pid_t = 0; +pub const ANY_PID: pid_t = 0; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum EventDescription { diff --git a/fish-rust/src/ffi.rs b/fish-rust/src/ffi.rs index df8d6ca22..f594cd5d0 100644 --- a/fish-rust/src/ffi.rs +++ b/fish-rust/src/ffi.rs @@ -126,17 +126,6 @@ include_cpp! { generate!("exec_subshell_ffi") - generate!("function_properties_t") - generate!("function_properties_ref_t") - generate!("function_get_props_autoload") - generate!("function_get_definition_file") - generate!("function_get_copy_definition_file") - generate!("function_get_definition_lineno") - generate!("function_get_copy_definition_lineno") - generate!("function_get_annotated_definition") - generate!("function_is_copy") - generate!("function_exists") - generate!("rgb_color_t") generate_pod!("color24_t") generate!("colorize_shell") @@ -153,8 +142,8 @@ include_cpp! { generate!("history_session_id") generate!("reader_change_cursor_selection_mode") generate!("reader_set_autosuggestion_enabled_ffi") - generate!("function_invalidate_path") generate!("complete_invalidate_path") + generate!("complete_add_wrapper") generate!("update_wait_on_escape_ms_ffi") generate!("autoload_t") generate!("make_autoload_ffi") @@ -351,7 +340,6 @@ impl Repin for job_t {} impl Repin for output_stream_t {} impl Repin for parser_t {} impl Repin for process_t {} -impl Repin for function_properties_ref_t {} impl Repin for wcstring_list_ffi_t {} pub use autocxx::c_int; diff --git a/fish-rust/src/function.rs b/fish-rust/src/function.rs index 8dcef01c1..77688c731 100644 --- a/fish-rust/src/function.rs +++ b/fish-rust/src/function.rs @@ -641,6 +641,7 @@ mod function_ffi { type FunctionPropertiesRefFFI; fn definition_file(&self) -> UniquePtr; + fn definition_lineno(&self) -> i32; fn copy_definition_lineno(&self) -> i32; fn shadow_scope(&self) -> bool; fn named_arguments(&self) -> UniquePtr; diff --git a/fish-rust/src/wait_handle.rs b/fish-rust/src/wait_handle.rs index 4665085dd..94af5a999 100644 --- a/fish-rust/src/wait_handle.rs +++ b/fish-rust/src/wait_handle.rs @@ -155,6 +155,11 @@ impl WaitHandle { assert!(!self.is_completed(), "wait handle already completed"); self.status.set(Some(status)); } + + /// \return the status, or None if not yet completed. + pub fn status(&self) -> Option { + self.status.get() + } } impl WaitHandle { diff --git a/src/ast.h b/src/ast.h index 088d65b5c..c171c7bf4 100644 --- a/src/ast.h +++ b/src/ast.h @@ -61,11 +61,13 @@ using while_header_t = WhileHeader; #else struct Ast; struct NodeFfi; +struct BlockStatement; namespace ast { using ast_t = Ast; +using block_statement_t = BlockStatement; + struct argument_t; -struct block_statement_t; struct statement_t; struct string_t; struct maybe_newlines_t; diff --git a/src/builtins/function.cpp b/src/builtins/function.cpp deleted file mode 100644 index 56e966775..000000000 --- a/src/builtins/function.cpp +++ /dev/null @@ -1,333 +0,0 @@ -// Implementation of the function builtin. -#include "config.h" // IWYU pragma: keep - -#include "function.h" - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "../builtin.h" -#include "../common.h" -#include "../complete.h" -#include "../env.h" -#include "../event.h" -#include "../fallback.h" // IWYU pragma: keep -#include "../function.h" -#include "../io.h" -#include "../maybe.h" -#include "../null_terminated_array.h" -#include "../parse_tree.h" -#include "../parser.h" -#include "../parser_keywords.h" -#include "../proc.h" -#include "../signals.h" -#include "../wgetopt.h" -#include "../wutil.h" // IWYU pragma: keep -#include "cxx.h" - -namespace { -struct function_cmd_opts_t { - bool print_help = false; - bool shadow_scope = true; - wcstring description; - std::vector events; - std::vector named_arguments; - std::vector inherit_vars; - std::vector wrap_targets; -}; -} // namespace - -// This command is atypical in using the "-" (RETURN_IN_ORDER) option for flag parsing. -// This is needed due to the semantics of the -a/--argument-names flag. -static const wchar_t *const short_options = L"-:a:d:e:hj:p:s:v:w:SV:"; -static const struct woption long_options[] = {{L"description", required_argument, 'd'}, - {L"on-signal", required_argument, 's'}, - {L"on-job-exit", required_argument, 'j'}, - {L"on-process-exit", required_argument, 'p'}, - {L"on-variable", required_argument, 'v'}, - {L"on-event", required_argument, 'e'}, - {L"wraps", required_argument, 'w'}, - {L"help", no_argument, 'h'}, - {L"argument-names", required_argument, 'a'}, - {L"no-scope-shadowing", no_argument, 'S'}, - {L"inherit-variable", required_argument, 'V'}, - {}}; - -/// \return the internal_job_id for a pid, or 0 if none. -/// This looks through both active and finished jobs. -static internal_job_id_t job_id_for_pid(pid_t pid, parser_t &parser) { - if (const auto *job = parser.job_get_from_pid(pid)) { - return job->internal_job_id; - } - return parser.get_wait_handles_ffi()->get_job_id_by_pid(pid); -} - -static int parse_cmd_opts(function_cmd_opts_t &opts, int *optind, //!OCLINT(high ncss method) - int argc, const wchar_t **argv, parser_t &parser, io_streams_t &streams) { - const wchar_t *cmd = L"function"; - int opt; - wgetopter_t w; - bool handling_named_arguments = false; - while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, nullptr)) != -1) { - if (opt != 'a' && opt != 1) handling_named_arguments = false; - switch (opt) { - case 1: { - if (handling_named_arguments) { - opts.named_arguments.push_back(w.woptarg); - break; - } else { - streams.err.append_format(_(L"%ls: %ls: unexpected positional argument"), cmd, - w.woptarg); - return STATUS_INVALID_ARGS; - } - } - case 'd': { - opts.description = w.woptarg; - break; - } - case 's': { - int sig = wcs2sig(w.woptarg); - if (sig == -1) { - streams.err.append_format(_(L"%ls: Unknown signal '%ls'"), cmd, w.woptarg); - return STATUS_INVALID_ARGS; - } - event_description_t event_desc; - event_desc.typ = event_type_t::signal; - event_desc.signal = sig; - opts.events.push_back(std::move(event_desc)); - break; - } - case 'v': { - if (!valid_var_name(w.woptarg)) { - streams.err.append_format(BUILTIN_ERR_VARNAME, cmd, w.woptarg); - return STATUS_INVALID_ARGS; - } - - event_description_t event_desc; - event_desc.typ = event_type_t::variable; - event_desc.str_param1 = std::make_unique(w.woptarg); - opts.events.push_back(std::move(event_desc)); - break; - } - case 'e': { - event_description_t event_desc; - event_desc.typ = event_type_t::generic; - event_desc.str_param1 = std::make_unique(w.woptarg); - opts.events.push_back(std::move(event_desc)); - break; - } - case 'j': - case 'p': { - event_description_t e; - e.typ = event_type_t::any; - - if ((opt == 'j') && (wcscasecmp(w.woptarg, L"caller") == 0)) { - internal_job_id_t caller_id = - parser.libdata().is_subshell ? parser.libdata().caller_id : 0; - if (caller_id == 0) { - streams.err.append_format( - _(L"%ls: calling job for event handler not found"), cmd); - return STATUS_INVALID_ARGS; - } - e.typ = event_type_t::caller_exit; - e.caller_id = caller_id; - } else if ((opt == 'p') && (wcscasecmp(w.woptarg, L"%self") == 0)) { - e.typ = event_type_t::process_exit; - e.pid = getpid(); - } else { - pid_t pid = fish_wcstoi(w.woptarg); - if (errno || pid < 0) { - streams.err.append_format(_(L"%ls: %ls: invalid process id"), cmd, - w.woptarg); - return STATUS_INVALID_ARGS; - } - if (opt == 'p') { - e.typ = event_type_t::process_exit; - e.pid = pid; - } else { - e.typ = event_type_t::job_exit; - e.pid = pid; - e.internal_job_id = job_id_for_pid(pid, parser); - } - } - opts.events.push_back(std::move(e)); - break; - } - case 'a': { - handling_named_arguments = true; - opts.named_arguments.push_back(w.woptarg); - break; - } - case 'S': { - opts.shadow_scope = false; - break; - } - case 'w': { - opts.wrap_targets.push_back(w.woptarg); - break; - } - case 'V': { - if (!valid_var_name(w.woptarg)) { - streams.err.append_format(BUILTIN_ERR_VARNAME, cmd, w.woptarg); - return STATUS_INVALID_ARGS; - } - opts.inherit_vars.push_back(w.woptarg); - break; - } - case 'h': { - opts.print_help = true; - break; - } - case ':': { - builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]); - return STATUS_INVALID_ARGS; - } - case '?': { - builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]); - return STATUS_INVALID_ARGS; - } - default: { - DIE("unexpected retval from wgetopt_long"); - } - } - } - - *optind = w.woptind; - return STATUS_CMD_OK; -} - -static int validate_function_name(int argc, const wchar_t *const *argv, wcstring &function_name, - const wchar_t *cmd, io_streams_t &streams) { - if (argc < 2) { - // This is currently impossible but let's be paranoid. - streams.err.append_format(_(L"%ls: function name required"), cmd); - return STATUS_INVALID_ARGS; - } - - function_name = argv[1]; - if (!valid_func_name(function_name)) { - streams.err.append_format(_(L"%ls: %ls: invalid function name"), cmd, - function_name.c_str()); - return STATUS_INVALID_ARGS; - } - - if (parser_keywords_is_reserved(function_name)) { - streams.err.append_format(_(L"%ls: %ls: cannot use reserved keyword as function name"), cmd, - function_name.c_str()); - return STATUS_INVALID_ARGS; - } - - return STATUS_CMD_OK; -} - -/// Define a function. Calls into `function.cpp` to perform the heavy lifting of defining a -/// function. -int builtin_function(parser_t &parser, io_streams_t &streams, const std::vector &c_args, - const parsed_source_ref_t &source, const ast::block_statement_t &func_node) { - assert(source.has_value() && "Missing source in builtin_function"); - // The wgetopt function expects 'function' as the first argument. Make a new wcstring_list with - // that property. This is needed because this builtin has a different signature than the other - // builtins. - std::vector args = {L"function"}; - args.insert(args.end(), c_args.begin(), c_args.end()); - - null_terminated_array_t argv_array(args); - const wchar_t **argv = argv_array.get(); - const wchar_t *cmd = argv[0]; - int argc = builtin_count_args(argv); - - // A valid function name has to be the first argument. - wcstring function_name; - int retval = validate_function_name(argc, argv, function_name, cmd, streams); - if (retval != STATUS_CMD_OK) return retval; - argv++; - argc--; - - function_cmd_opts_t opts; - int optind; - retval = parse_cmd_opts(opts, &optind, argc, argv, parser, streams); - if (retval != STATUS_CMD_OK) return retval; - - if (opts.print_help) { - builtin_print_error_trailer(parser, streams.err, cmd); - return STATUS_CMD_OK; - } - - if (argc != optind) { - if (!opts.named_arguments.empty()) { - for (int i = optind; i < argc; i++) { - if (!valid_var_name(argv[i])) { - streams.err.append_format(BUILTIN_ERR_VARNAME, cmd, argv[i]); - return STATUS_INVALID_ARGS; - } - opts.named_arguments.push_back(argv[i]); - } - } else { - streams.err.append_format(_(L"%ls: %ls: unexpected positional argument"), cmd, - argv[optind]); - return STATUS_INVALID_ARGS; - } - } - - // We have what we need to actually define the function. - auto props = std::make_shared(); - props->shadow_scope = opts.shadow_scope; - props->named_arguments = std::move(opts.named_arguments); - props->parsed_source = source.clone(); - props->func_node = &func_node; - props->description = opts.description; - props->definition_file = parser.libdata().current_filename; - - // Populate inherit_vars. - for (const wcstring &name : opts.inherit_vars) { - if (auto var = parser.vars().get(name)) { - props->inherit_vars[name] = var->as_list(); - } - } - - // Add the function itself. - function_add(function_name, props); - - // Handle wrap targets by creating the appropriate completions. - for (const wcstring &wt : opts.wrap_targets) { - complete_add_wrapper(function_name, wt); - } - - // Add any event handlers. - for (const event_description_t &ed : opts.events) { - event_add_handler(ed, function_name); - } - - // If there is an --on-process-exit or --on-job-exit event handler for some pid, and that - // process has already exited, run it immediately (#7210). - for (const event_description_t &ed : opts.events) { - if (ed.typ == event_type_t::process_exit) { - pid_t pid = ed.pid; - if (pid == EVENT_ANY_PID) continue; - int status{}; - uint64_t internal_job_id{}; - if (parser.get_wait_handles_ffi()->try_get_status_and_job_id(pid, true, status, - internal_job_id)) { - event_fire(parser, *new_event_process_exit(pid, status)); - } - } else if (ed.typ == event_type_t::job_exit) { - pid_t pid = ed.pid; - if (pid == EVENT_ANY_PID) continue; - int status{}; - uint64_t internal_job_id{}; - if (parser.get_wait_handles_ffi()->try_get_status_and_job_id(pid, true, status, - internal_job_id)) { - event_fire(parser, *new_event_job_exit(pid, internal_job_id)); - } - } - } - - return STATUS_CMD_OK; -} diff --git a/src/builtins/function.h b/src/builtins/function.h deleted file mode 100644 index 2eb0a8259..000000000 --- a/src/builtins/function.h +++ /dev/null @@ -1,14 +0,0 @@ -// Prototypes for executing builtin_function function. -#ifndef FISH_BUILTIN_FUNCTION_H -#define FISH_BUILTIN_FUNCTION_H - -#include "../ast.h" -#include "../common.h" -#include "../parse_tree.h" - -class parser_t; -struct io_streams_t; - -int builtin_function(parser_t &parser, io_streams_t &streams, const std::vector &c_args, - const parsed_source_ref_t &source, const ast::block_statement_t &func_node); -#endif diff --git a/src/builtins/functions.cpp b/src/builtins/functions.cpp index 3e62712e1..51d660b7f 100644 --- a/src/builtins/functions.cpp +++ b/src/builtins/functions.cpp @@ -139,26 +139,29 @@ static int report_function_metadata(const wcstring &funcname, bool verbose, io_s wcstring copy_path = L"n/a"; int copy_line_number = 0; - if (auto props = function_get_props_autoload(funcname, parser)) { - if (props->definition_file) { - path = *props->definition_file; - autoloaded = props->is_autoload ? L"autoloaded" : L"not-autoloaded"; + if (auto mprops = function_get_props_autoload(funcname, parser)) { + const auto &props = *mprops; + if (auto def_file = props->definition_file()) { + path = std::move(*def_file); + autoloaded = props->is_autoload() ? L"autoloaded" : L"not-autoloaded"; line_number = props->definition_lineno(); } else { path = L"stdin"; } - is_copy = props->is_copy; + is_copy = props->is_copy(); - if (props->copy_definition_file) { - copy_path = *props->copy_definition_file; - copy_line_number = props->copy_definition_lineno; + auto definition_file = props->copy_definition_file(); + if (definition_file) { + copy_path = *definition_file; + copy_line_number = props->copy_definition_lineno(); } else { copy_path = L"stdin"; } - shadows_scope = props->shadow_scope ? L"scope-shadowing" : L"no-scope-shadowing"; - description = escape_string(props->description, ESCAPE_NO_PRINTABLES | ESCAPE_NO_QUOTED); + shadows_scope = props->shadow_scope() ? L"scope-shadowing" : L"no-scope-shadowing"; + description = + escape_string(*props->get_description(), ESCAPE_NO_PRINTABLES | ESCAPE_NO_QUOTED); } if (metadata_as_comments) { @@ -298,7 +301,9 @@ maybe_t builtin_functions(parser_t &parser, io_streams_t &streams, const wc } if (opts.list || argc == optind) { - std::vector names = function_get_names(opts.show_hidden); + wcstring_list_ffi_t names_ffi{}; + function_get_names(opts.show_hidden, names_ffi); + std::vector names = std::move(names_ffi.vals); std::sort(names.begin(), names.end()); bool is_screen = !streams.out_is_redirected && isatty(STDOUT_FILENO); if (is_screen) { @@ -375,7 +380,8 @@ maybe_t builtin_functions(parser_t &parser, io_streams_t &streams, const wc if (!opts.no_metadata) { report_function_metadata(funcname, opts.verbose, streams, parser, true); } - wcstring def = func->annotated_definition(funcname); + std::unique_ptr def_ptr = (*func)->annotated_definition(funcname); + wcstring def = std::move(*def_ptr); if (!streams.out_is_redirected && isatty(STDOUT_FILENO)) { std::vector colors; diff --git a/src/complete.cpp b/src/complete.cpp index 0603ce3d4..64bbffa09 100644 --- a/src/complete.cpp +++ b/src/complete.cpp @@ -622,7 +622,8 @@ void completer_t::complete_cmd_desc(const wcstring &str) { /// Returns a description for the specified function, or an empty string if none. static wcstring complete_function_desc(const wcstring &fn) { if (auto props = function_get_props(fn)) { - return props->description; + std::unique_ptr desc = (*props)->get_description(); + return std::move(*desc); } return wcstring{}; } @@ -659,8 +660,9 @@ void completer_t::complete_cmd(const wcstring &str_cmd) { if (str_cmd.empty() || (str_cmd.find(L'/') == wcstring::npos && str_cmd.at(0) != L'~')) { bool include_hidden = !str_cmd.empty() && str_cmd.at(0) == L'_'; - std::vector names = function_get_names(include_hidden); - for (wcstring &name : names) { + wcstring_list_ffi_t names{}; + function_get_names(include_hidden, names); + for (wcstring &name : names.vals) { // Append all known matching functions append_completion(&possible_comp, std::move(name)); } diff --git a/src/env.cpp b/src/env.cpp index da0c1cbb1..1647ac412 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -385,6 +385,10 @@ std::vector> env_stack_t::universal_sync(bool always) { return std::move(result.events); } +void env_stack_t::apply_inherited_ffi(const function_properties_t &props) { + impl_->apply_inherited_ffi(props); +} + statuses_t env_stack_t::get_last_statuses() const { auto statuses_ffi = impl_->get_last_statuses(); statuses_t res{}; diff --git a/src/env.h b/src/env.h index 54f5c7c15..e94f3731f 100644 --- a/src/env.h +++ b/src/env.h @@ -18,6 +18,7 @@ #include "wutil.h" struct event_list_ffi_t; +struct function_properties_t; #if INCLUDE_RUST_HEADERS #include "env/env_ffi.rs.h" @@ -293,6 +294,9 @@ class env_stack_t final : public environment_t { /// \return a list of events for changed variables. std::vector> universal_sync(bool always); + /// Applies inherited variables in preparation for executing a function. + void apply_inherited_ffi(const function_properties_t &props); + // Compatibility hack; access the "environment stack" from back when there was just one. static const std::shared_ptr &principal_ref(); static env_stack_t &principal() { return *principal_ref(); } diff --git a/src/exec.cpp b/src/exec.cpp index ae37b53bb..96fff3782 100644 --- a/src/exec.cpp +++ b/src/exec.cpp @@ -583,7 +583,7 @@ static block_t *function_prepare_environment(parser_t &parser, std::vectorvals) { if (idx < argv.size()) { vars.set_one(named_arg, ENV_LOCAL | ENV_USER, argv.at(idx)); } else { @@ -601,9 +602,10 @@ static block_t *function_prepare_environment(parser_t &parser, std::vectorargv0()); return proc_performer_t{}; } + auto props_box = std::make_shared>(props.acquire()); const std::vector &argv = p->argv(); return [=](parser_t &parser) { // Pull out the job list from the function. - const ast::job_list_t &body = props->func_node->jobs(); - const block_t *fb = function_prepare_environment(parser, argv, *props); - auto res = parser.eval_node(*props->parsed_source, body, io_chain, job_group); + const auto *func = reinterpret_cast( + (*props_box)->get_block_statement_node()); + const ast::job_list_t &body = func->jobs(); + const block_t *fb = function_prepare_environment(parser, argv, **props_box); + const auto parsed_source_raw = (*props_box)->parsed_source(); + const auto parsed_source_box = rust::Box::from_raw( + reinterpret_cast(parsed_source_raw)); + auto res = parser.eval_node(*parsed_source_box, body, io_chain, job_group); function_restore_environment(parser, fb); // If the function did not execute anything, treat it as success. diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index e96e99edb..def1bf7b6 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -2444,23 +2444,6 @@ static void test_autoload() { autoload_tester_t::run_test(); } -// Construct function properties for testing. -static std::shared_ptr make_test_func_props() { - auto ret = std::make_shared(); - ret->parsed_source = parse_source(L"function stuff; end", parse_flag_none, nullptr); - assert(ret->parsed_source->has_value() && "Failed to parse"); - for (auto ast_traversal = new_ast_traversal(*ret->parsed_source->ast().top());;) { - auto node = ast_traversal->next(); - if (!node->has_value()) break; - if (const auto *s = node->try_as_block_statement()) { - ret->func_node = s; - break; - } - } - assert(ret->func_node && "Unable to find block statement"); - return ret; -} - static void test_wildcards() { say(L"Testing wildcards"); do_test(!wildcard_has(L"")); @@ -2486,7 +2469,6 @@ static void test_wildcards() { static void test_complete() { say(L"Testing complete"); - auto func_props = make_test_func_props(); struct test_complete_vars_t : environment_t { std::vector get_names(env_mode_flags_t flags) const override { UNUSED(flags); @@ -2616,7 +2598,7 @@ static void test_complete() { #endif // Add a function and test completing it in various ways. - function_add(L"scuttlebutt", func_props); + parser->eval(L"function scuttlebutt; end", {}); // Complete a function name. completions = do_complete(L"echo (scuttlebut", {}); @@ -2705,7 +2687,7 @@ static void test_complete() { completions.clear(); // Test abbreviations. - function_add(L"testabbrsonetwothreefour", func_props); + parser->eval(L"function testabbrsonetwothreefour; end", {}); abbrs_get_set()->add(L"somename", L"testabbrsonetwothreezero", L"expansion", abbrs_position_t::command, false); completions = complete(L"testabbrsonetwothree", {}, parser->context()); diff --git a/src/function.cpp b/src/function.cpp index a647f6462..2f2b6df9b 100644 --- a/src/function.cpp +++ b/src/function.cpp @@ -6,438 +6,19 @@ #include "function.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ast.h" -#include "autoload.h" #include "common.h" -#include "complete.h" -#include "env.h" -#include "event.h" -#include "fallback.h" // IWYU pragma: keep -#include "maybe.h" -#include "parse_constants.h" -#include "parser.h" -#include "parser_keywords.h" -#include "signals.h" -#include "wcstringutil.h" -#include "wutil.h" // IWYU pragma: keep -namespace { - -/// Type wrapping up the set of all functions. -/// There's only one of these; it's managed by a lock. -struct function_set_t { - /// The map of all functions by name. - std::unordered_map funcs; - - /// Tombstones for functions that should no longer be autoloaded. - std::unordered_set autoload_tombstones; - - /// The autoloader for our functions. - autoload_t autoloader{L"fish_function_path"}; - - /// Remove a function. - /// \return true if successful, false if it doesn't exist. - bool remove(const wcstring &name); - - /// Get the properties for a function, or nullptr if none. - function_properties_ref_t get_props(const wcstring &name) const { - auto iter = funcs.find(name); - return iter == funcs.end() ? nullptr : iter->second; +maybe_t> function_get_props(const wcstring &name) { + if (auto *ptr = function_get_props_raw(name)) { + return rust::Box::from_raw(ptr); } - - /// \return true if we should allow autoloading a given function. - bool allow_autoload(const wcstring &name) const; - - function_set_t() = default; -}; - -/// The big set of all functions. -static owning_lock function_set; - -bool function_set_t::allow_autoload(const wcstring &name) const { - // Prohibit autoloading if we have a non-autoload (explicit) function, or if the function is - // tombstoned. - auto props = get_props(name); - bool has_explicit_func = props && !props->is_autoload; - bool is_tombstoned = autoload_tombstones.count(name) > 0; - return !has_explicit_func && !is_tombstoned; -} -} // namespace - -/// \return a copy of some function props, in a new shared_ptr. -static std::shared_ptr copy_props(const function_properties_ref_t &props) { - assert(props && "Null props"); - return std::make_shared(*props); + return none(); } -/// Make sure that if the specified function is a dynamically loaded function, it has been fully -/// loaded. -/// Note this executes fish script code. -bool function_load(const wcstring &name, parser_t &parser) { - parser.assert_can_execute(); - maybe_t path_to_autoload; - // Note we can't autoload while holding the funcset lock. - // Lock around a local region. - { - auto funcset = function_set.acquire(); - if (funcset->allow_autoload(name)) { - path_to_autoload = funcset->autoloader.resolve_command(name, env_stack_t::globals()); - } +maybe_t> function_get_props_autoload(const wcstring &name, + parser_t &parser) { + if (auto *ptr = function_get_props_autoload_raw(name, parser)) { + return rust::Box::from_raw(ptr); } - - // Release the lock and perform any autoload, then reacquire the lock and clean up. - if (path_to_autoload) { - // Crucially, the lock is acquired after perform_autoload(). - autoload_t::perform_autoload(*path_to_autoload, parser); - function_set.acquire()->autoloader.mark_autoload_finished(name); - } - return path_to_autoload.has_value(); -} - -/// Insert a list of all dynamically loaded functions into the specified list. -static void autoload_names(std::unordered_set &names, bool get_hidden) { - size_t i; - - // TODO: justify this. - auto &vars = env_stack_t::principal(); - const auto path_var = vars.get_unless_empty(L"fish_function_path"); - if (!path_var) return; - - const std::vector &path_list = path_var->as_list(); - - for (i = 0; i < path_list.size(); i++) { - const wcstring &ndir_str = path_list.at(i); - dir_iter_t dir(ndir_str); - if (!dir.valid()) continue; - - while (const auto *entry = dir.next()) { - const wchar_t *fn = entry->name.c_str(); - const wchar_t *suffix; - if (!get_hidden && fn[0] == L'_') continue; - - suffix = std::wcsrchr(fn, L'.'); - // We need a ".fish" *suffix*, it can't be the entire name. - if (suffix && suffix != fn && (std::wcscmp(suffix, L".fish") == 0)) { - // Also ignore directories. - if (!entry->is_dir()) { - wcstring name(fn, suffix - fn); - names.insert(name); - } - } - } - } -} - -void function_add(wcstring name, std::shared_ptr props) { - assert(props && "Null props"); - auto funcset = function_set.acquire(); - - // Historical check. TODO: rationalize this. - if (name.empty()) { - return; - } - - // Remove the old function. - funcset->remove(name); - - // Check if this is a function that we are autoloading. - props->is_autoload = funcset->autoloader.autoload_in_progress(name); - - // Create and store a new function. - auto ins = funcset->funcs.emplace(std::move(name), std::move(props)); - assert(ins.second && "Function should not already be present in the table"); - (void)ins; -} - -function_properties_ref_t function_get_props(const wcstring &name) { - if (parser_keywords_is_reserved(name)) return nullptr; - return function_set.acquire()->get_props(name); -} - -wcstring function_get_definition_file(const function_properties_t &props) { - return props.definition_file ? *props.definition_file : L""; -} - -wcstring function_get_copy_definition_file(const function_properties_t &props) { - return props.copy_definition_file ? *props.copy_definition_file : L""; -} -bool function_is_copy(const function_properties_t &props) { return props.is_copy; } -int function_get_definition_lineno(const function_properties_t &props) { - return props.definition_lineno(); -} -int function_get_copy_definition_lineno(const function_properties_t &props) { - return props.copy_definition_lineno; -} - -wcstring function_get_annotated_definition(const function_properties_t &props, - const wcstring &name) { - return props.annotated_definition(name); -} - -function_properties_ref_t function_get_props_autoload(const wcstring &name, parser_t &parser) { - parser.assert_can_execute(); - if (parser_keywords_is_reserved(name)) return nullptr; - function_load(name, parser); - return function_get_props(name); -} - -bool function_exists(const wcstring &cmd, parser_t &parser) { - parser.assert_can_execute(); - if (!valid_func_name(cmd)) return false; - return function_get_props_autoload(cmd, parser) != nullptr; -} - -bool function_exists_no_autoload(const wcstring &cmd) { - if (!valid_func_name(cmd)) return false; - if (parser_keywords_is_reserved(cmd)) return false; - auto funcset = function_set.acquire(); - - // Check if we either have the function, or it could be autoloaded. - return funcset->get_props(cmd) || funcset->autoloader.can_autoload(cmd); -} - -bool function_set_t::remove(const wcstring &name) { - size_t amt = funcs.erase(name); - if (amt > 0) { - event_remove_function_handlers(name); - } - return amt > 0; -} - -void function_remove(const wcstring &name) { - auto funcset = function_set.acquire(); - funcset->remove(name); - // Prevent (re-)autoloading this function. - funcset->autoload_tombstones.insert(name); -} - -// \return the body of a function (everything after the header, up to but not including the 'end'). -static wcstring get_function_body_source(const function_properties_t &props) { - // We want to preserve comments that the AST attaches to the header (#5285). - // Take everything from the end of the header to the 'end' keyword. - if (props.func_node->header().ptr()->try_source_range() && - props.func_node->end().try_source_range()) { - auto header_src = props.func_node->header().ptr()->source_range(); - auto end_kw_src = props.func_node->end().range(); - uint32_t body_start = header_src.start + header_src.length; - uint32_t body_end = end_kw_src.start; - assert(body_start <= body_end && "end keyword should come after header"); - return wcstring(props.parsed_source->src(), body_start, body_end - body_start); - } - return wcstring{}; -} - -void function_set_desc(const wcstring &name, const wcstring &desc, parser_t &parser) { - parser.assert_can_execute(); - function_load(name, parser); - auto funcset = function_set.acquire(); - auto iter = funcset->funcs.find(name); - if (iter != funcset->funcs.end()) { - // Note the description is immutable, as it may be accessed on another thread, so we copy - // the properties to modify it. - auto new_props = copy_props(iter->second); - new_props->description = desc; - iter->second = new_props; - } -} - -bool function_copy(const wcstring &name, const wcstring &new_name, parser_t &parser) { - auto filename = parser.current_filename(); - auto lineno = parser.get_lineno(); - - auto funcset = function_set.acquire(); - auto props = funcset->get_props(name); - if (!props) { - // No such function. - return false; - } - // Copy the function's props. - auto new_props = copy_props(props); - new_props->is_autoload = false; - new_props->is_copy = true; - new_props->copy_definition_file = filename; - new_props->copy_definition_lineno = lineno; - - // Note this will NOT overwrite an existing function with the new name. - // TODO: rationalize if this behavior is desired. - funcset->funcs.emplace(new_name, std::move(new_props)); - return true; -} - -std::vector function_get_names(bool get_hidden) { - std::unordered_set names; - auto funcset = function_set.acquire(); - autoload_names(names, get_hidden); - for (const auto &func : funcset->funcs) { - const wcstring &name = func.first; - - // Maybe skip hidden. - if (!get_hidden && (name.empty() || name.at(0) == L'_')) { - continue; - } - names.insert(name); - } - return std::vector(names.begin(), names.end()); -} - -void function_invalidate_path() { - // Remove all autoloaded functions and update the autoload path. - // Note we don't want to risk removal during iteration; we expect this to be called - // infrequently. - auto funcset = function_set.acquire(); - std::vector autoloadees; - for (const auto &kv : funcset->funcs) { - if (kv.second->is_autoload) { - autoloadees.push_back(kv.first); - } - } - for (const wcstring &name : autoloadees) { - funcset->remove(name); - } - funcset->autoloader.clear(); -} - -function_properties_t::function_properties_t() : parsed_source(empty_parsed_source_ref()) {} - -function_properties_t::function_properties_t(const function_properties_t &other) - : parsed_source(empty_parsed_source_ref()) { - *this = other; -} - -function_properties_t &function_properties_t::operator=(const function_properties_t &other) { - parsed_source = other.parsed_source->clone(); - func_node = other.func_node; - named_arguments = other.named_arguments; - description = other.description; - inherit_vars = other.inherit_vars; - shadow_scope = other.shadow_scope; - is_autoload = other.is_autoload; - definition_file = other.definition_file; - return *this; -} - -wcstring function_properties_t::annotated_definition(const wcstring &name) const { - wcstring out; - wcstring desc = this->localized_description(); - wcstring def = get_function_body_source(*this); - auto handlers = event_get_function_handler_descs(name); - - out.append(L"function "); - - // Typically we prefer to specify the function name first, e.g. "function foo --description bar" - // But if the function name starts with a -, we'll need to output it after all the options. - bool defer_function_name = (name.at(0) == L'-'); - if (!defer_function_name) { - out.append(escape_string(name)); - } - - // Output wrap targets. - for (const wcstring &wrap : complete_get_wrap_targets(name)) { - out.append(L" --wraps="); - out.append(escape_string(wrap)); - } - - if (!desc.empty()) { - out.append(L" --description "); - out.append(escape_string(desc)); - } - - if (!this->shadow_scope) { - out.append(L" --no-scope-shadowing"); - } - - for (const auto &d : handlers) { - switch (d.typ) { - case event_type_t::signal: { - append_format(out, L" --on-signal %ls", sig2wcs(d.signal)->c_str()); - break; - } - case event_type_t::variable: { - append_format(out, L" --on-variable %ls", d.str_param1->c_str()); - break; - } - case event_type_t::process_exit: { - append_format(out, L" --on-process-exit %d", d.pid); - break; - } - case event_type_t::job_exit: { - append_format(out, L" --on-job-exit %d", d.pid); - break; - } - case event_type_t::caller_exit: { - append_format(out, L" --on-job-exit caller"); - break; - } - case event_type_t::generic: { - append_format(out, L" --on-event %ls", d.str_param1->c_str()); - break; - } - case event_type_t::any: - default: { - DIE("unexpected next->typ"); - } - } - } - - const std::vector &named = this->named_arguments; - if (!named.empty()) { - append_format(out, L" --argument"); - for (const auto &name : named) { - append_format(out, L" %ls", name.c_str()); - } - } - - // Output the function name if we deferred it. - if (defer_function_name) { - out.append(L" -- "); - out.append(escape_string(name)); - } - - // Output any inherited variables as `set -l` lines. - for (const auto &kv : this->inherit_vars) { - // We don't know what indentation style the function uses, - // so we do what fish_indent would. - append_format(out, L"\n set -l %ls", kv.first.c_str()); - for (const auto &arg : kv.second) { - out.push_back(L' '); - out.append(escape_string(arg)); - } - } - out.push_back('\n'); - out.append(def); - - // Append a newline before the 'end', unless there already is one there. - if (!string_suffixes_string(L"\n", def)) { - out.push_back(L'\n'); - } - out.append(L"end\n"); - return out; -} - -const wchar_t *function_properties_t::localized_description() const { - if (description.empty()) return L""; - return _(description.c_str()); -} - -int function_properties_t::definition_lineno() const { - // return one plus the number of newlines at offsets less than the start of our function's - // statement (which includes the header). - // TODO: merge with line_offset_of_character_at_offset? - assert(func_node->try_source_range() && "Function has no source range"); - auto source_range = func_node->source_range(); - uint32_t func_start = source_range.start; - const wcstring &source = parsed_source->src(); - assert(func_start <= source.size() && "function start out of bounds"); - return 1 + std::count(source.begin(), source.begin() + func_start, L'\n'); + return none(); } diff --git a/src/function.h b/src/function.h index bce3a15b5..837bcddd5 100644 --- a/src/function.h +++ b/src/function.h @@ -4,120 +4,18 @@ #ifndef FISH_FUNCTION_H #define FISH_FUNCTION_H -#include -#include -#include - -#include "ast.h" -#include "common.h" -#include "parse_tree.h" +#include "cxx.h" +#include "maybe.h" +struct function_properties_t; class parser_t; -/// A function's constant properties. These do not change once initialized. -struct function_properties_t { - function_properties_t(); - function_properties_t(const function_properties_t &other); - function_properties_t &operator=(const function_properties_t &other); +#if INCLUDE_RUST_HEADERS +#include "function.rs.h" +#endif - /// Parsed source containing the function. - rust::Box parsed_source; - - /// Node containing the function statement, pointing into parsed_source. - /// We store block_statement, not job_list, so that comments attached to the header are - /// preserved. - const ast::block_statement_t *func_node; - - /// List of all named arguments for this function. - std::vector named_arguments; - - /// Description of the function. - wcstring description; - - /// Mapping of all variables that were inherited from the function definition scope to their - /// values. - std::map> inherit_vars; - - /// Set to true if invoking this function shadows the variables of the underlying function. - bool shadow_scope{true}; - - /// Whether the function was autoloaded. - bool is_autoload{false}; - - /// The file from which the function was created, or nullptr if not from a file. - filename_ref_t definition_file{}; - - /// Whether the function was copied. - bool is_copy{false}; - - /// The file from which the function was copied, or nullptr if not from a file. - filename_ref_t copy_definition_file{}; - - /// The line number where the specified function was copied. - int copy_definition_lineno{}; - - /// \return the description, localized via _. - const wchar_t *localized_description() const; - - /// \return the line number where the definition of the specified function started. - int definition_lineno() const; - - /// \return a definition of the function, annotated with properties like event handlers and wrap - /// targets. This is to support the 'functions' builtin. - /// Note callers must provide the function name, since the function does not know its own name. - wcstring annotated_definition(const wcstring &name) const; -}; - -// FIXME: Morally, this is const, but cxx doesn't get it -using function_properties_ref_t = std::shared_ptr; - -/// Add a function. This may mutate \p props to set is_autoload. -void function_add(wcstring name, std::shared_ptr props); - -/// Remove the function with the specified name. -void function_remove(const wcstring &name); - -/// \return the properties for a function, or nullptr if none. This does not trigger autoloading. -function_properties_ref_t function_get_props(const wcstring &name); - -/// Guff to work around cxx not getting function_properties_t. -wcstring function_get_definition_file(const function_properties_t &props); -wcstring function_get_copy_definition_file(const function_properties_t &props); -bool function_is_copy(const function_properties_t &props); -int function_get_definition_lineno(const function_properties_t &props); -int function_get_copy_definition_lineno(const function_properties_t &props); -wcstring function_get_annotated_definition(const function_properties_t &props, - const wcstring &name); - -/// \return the properties for a function, or nullptr if none, perhaps triggering autoloading. -function_properties_ref_t function_get_props_autoload(const wcstring &name, parser_t &parser); - -/// Try autoloading a function. -/// \return true if something new was autoloaded, false if it was already loaded or did not exist. -bool function_load(const wcstring &name, parser_t &parser); - -/// Sets the description of the function with the name \c name. -/// This triggers autoloading. -void function_set_desc(const wcstring &name, const wcstring &desc, parser_t &parser); - -/// Returns true if the function named \p cmd exists. -/// This may autoload. -bool function_exists(const wcstring &cmd, parser_t &parser); - -/// Returns true if the function \p cmd either is loaded, or exists on disk in an autoload -/// directory. -bool function_exists_no_autoload(const wcstring &cmd); - -/// Returns all function names. -/// -/// \param get_hidden whether to include hidden functions, i.e. ones starting with an underscore. -std::vector function_get_names(bool get_hidden); - -/// Creates a new function using the same definition as the specified function. Returns true if copy -/// is successful. -bool function_copy(const wcstring &name, const wcstring &new_name, parser_t &parser); - -/// Observes that fish_function_path has changed. -void function_invalidate_path(); +maybe_t> function_get_props(const wcstring &name); +maybe_t> function_get_props_autoload(const wcstring &name, + parser_t &parser); #endif diff --git a/src/parse_execution.cpp b/src/parse_execution.cpp index 483724882..be26befc1 100644 --- a/src/parse_execution.cpp +++ b/src/parse_execution.cpp @@ -20,7 +20,7 @@ #include "ast.h" #include "builtin.h" -#include "builtins/function.h" +#include "builtins/function.rs.h" #include "common.h" #include "complete.h" #include "env.h" @@ -388,21 +388,23 @@ end_execution_reason_t parse_execution_context_t::run_function_statement( const ast::block_statement_t &statement, const ast::function_header_t &header) { using namespace ast; // Get arguments. - std::vector arguments; + wcstring_list_ffi_t arguments; ast_args_list_t arg_nodes = get_argument_nodes(header.args()); arg_nodes.insert(arg_nodes.begin(), &header.first_arg()); end_execution_reason_t result = - this->expand_arguments_from_nodes(arg_nodes, &arguments, failglob); + this->expand_arguments_from_nodes(arg_nodes, &arguments.vals, failglob); if (result != end_execution_reason_t::ok) { return result; } - trace_if_enabled(*parser, L"function", arguments); + trace_if_enabled(*parser, L"function", arguments.vals); null_output_stream_t outs; string_output_stream_t errs; io_streams_t streams(outs, errs); - int err_code = builtin_function(*parser, streams, arguments, *pstree, statement); + int err_code = builtin_function_ffi(*parser, streams, arguments, + reinterpret_cast(&*pstree), statement); + parser->libdata().status_count++; parser->set_last_statuses(statuses_t::just(err_code)); diff --git a/src/parser.cpp b/src/parser.cpp index 704cb6928..e5c49f67b 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -408,7 +408,7 @@ filename_ref_t parser_t::current_filename() const { for (const auto &b : block_list) { if (b.is_function_call()) { auto props = function_get_props(b.function_name); - return props ? props->definition_file : nullptr; + return props ? (*props)->definition_file() : nullptr; } else if (b.type() == block_type_t::source) { return b.sourced_file; } @@ -537,6 +537,14 @@ job_t *parser_t::job_get_from_pid(int pid, size_t &job_pos) const { return nullptr; } +const wcstring *library_data_t::get_current_filename() const { + if (current_filename) { + return &*current_filename; + } else { + return nullptr; + } +} + library_data_pod_t *parser_t::ffi_libdata_pod() { return &library_data; } job_t *parser_t::ffi_job_get_from_pid(int pid) const { return job_get_from_pid(pid); } diff --git a/src/parser.h b/src/parser.h index 287cb7e1b..5c5cdc411 100644 --- a/src/parser.h +++ b/src/parser.h @@ -225,9 +225,10 @@ struct library_data_t : public library_data_pod_t { wcstring commandline; } status_vars; - public: + public: wcstring get_status_vars_command() const { return status_vars.command; } wcstring get_status_vars_commandline() const { return status_vars.commandline; } + const wcstring *get_current_filename() const; // may return nullptr if None }; /// The result of parser_t::eval family.