Mercurial > octave
view libinterp/octave-value/ov-usr-fcn.cc @ 28430:5bfa8e018704 stable
store local init vars for anonymous functions in handle, not function object
This change is step toward revamping function handles by storing
variable init values for anonymous functions in function handle
objects instead of in the corresponding functions.
* call-stack.h, call-stack.cc (call_stack::push): New overload that
accepts local variable map in addition to function object.
* stack-frame.h (user_fcn_stack_frame::user_fcn_stack_frame):
New constructor that accepts local variable map in addition to
function object.
(stack_frame::local_vars_map): New typedef.
* ov-fcn-handle.h, ov-fcn-handle.cc (octave_fcn_handle::m_local_vars):
New data member.
(octave_fcn_handle::octave_fcn_handle): Update existing constructors
and provide new one to construct handle from function object and local
variable map.
(octave_fcn_handle::call): If m_local_vars is defined, push stack
frame with that info and execute function here.
(octave_fcn_handle::workspace): Create workspace struct from
m_local_vars instead of getting that info from the function object.
(octave_fcn_handle::parse_anon_fcn_handle): Copy m_local_vars from new
function handle object.
(octave_fcn_handle::save_ascii, octave_fcn_handle::save_binary,
octave_fcn_handle::save_hdf5): Use m_local_vars instead of getting
info from function object.
* ov-usr-fcn.h, ov-usr-fcn.cc (octave_user_function::local_vars_map):
Delete typedef.
(octave_user_function::m_local_var_init_vals): Delete data member and
all uses.
(octave_user_function::local_var_init_vals): Delete.
* pt-eval.h, pt-eval.cc (tree_evaluator::push_stack_frame):
New overload that accepts local variable map and user function.
(tree_evaluator::init_local_fcn_vars): Delete function and all uses.
* pt-fcn-handle.cc (tree_anon_fcn_handle::evaluate): Store local
variables in function handle object instead of function object.
author | John W. Eaton <jwe@octave.org> |
---|---|
date | Mon, 30 Mar 2020 15:14:10 -0400 |
parents | 8eb8ba8aff9a |
children | 0a5b15007766 |
line wrap: on
line source
//////////////////////////////////////////////////////////////////////// // // Copyright (C) 1996-2020 The Octave Project Developers // // See the file COPYRIGHT.md in the top-level directory of this // distribution or <https://octave.org/copyright/>. // // This file is part of Octave. // // Octave is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Octave is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Octave; see the file COPYING. If not, see // <https://www.gnu.org/licenses/>. // //////////////////////////////////////////////////////////////////////// #if defined (HAVE_CONFIG_H) # include "config.h" #endif #include <sstream> #include "file-info.h" #include "file-stat.h" #include "str-vec.h" #include "builtin-defun-decls.h" #include "defaults.h" #include "Cell.h" #include "defun.h" #include "error.h" #include "errwarn.h" #include "input.h" #include "ovl.h" #include "ov-usr-fcn.h" #include "ov.h" #include "pager.h" #include "pt-eval.h" #include "pt-jit.h" #include "pt-jump.h" #include "pt-misc.h" #include "pt-pr-code.h" #include "pt-stmt.h" #include "pt-walk.h" #include "symtab.h" #include "interpreter-private.h" #include "interpreter.h" #include "unwind-prot.h" #include "utils.h" #include "parse.h" #include "profiler.h" #include "variables.h" #include "ov-fcn-handle.h" // Whether to optimize subsasgn method calls. static bool Voptimize_subsasgn_calls = true; octave_user_code::~octave_user_code (void) { // This function is no longer valid, so remove the pointer to it from // the corresponding scope. // FIXME: would it be better to use shared/weak pointers for this job // instead of storing a bare pointer in the scope object? m_scope.set_user_code (nullptr); // FIXME: shouldn't this happen automatically when deleting cmd_list? if (cmd_list) { octave::event_manager& evmgr = octave::__get_event_manager__ ("octave_user_code::~octave_user_code"); cmd_list->remove_all_breakpoints (evmgr, file_name); } delete cmd_list; delete m_file_info; } void octave_user_code::get_file_info (void) { m_file_info = new octave::file_info (file_name); octave::sys::file_stat fs (file_name); if (fs && (fs.mtime () > time_parsed ())) warning ("function file '%s' changed since it was parsed", file_name.c_str ()); } std::string octave_user_code::get_code_line (size_t line) { if (! m_file_info) get_file_info (); return m_file_info->get_line (line); } std::deque<std::string> octave_user_code::get_code_lines (size_t line, size_t num_lines) { if (! m_file_info) get_file_info (); return m_file_info->get_lines (line, num_lines); } void octave_user_code::cache_function_text (const std::string& text, const octave::sys::time& timestamp) { if (m_file_info) delete m_file_info; if (timestamp > time_parsed ()) warning ("help text for function is newer than function"); m_file_info = new octave::file_info (text, timestamp); } std::map<std::string, octave_value> octave_user_code::subfunctions (void) const { return std::map<std::string, octave_value> (); } octave_value octave_user_code::dump (void) const { std::map<std::string, octave_value> m = {{ "scope_info", m_scope ? m_scope.dump () : "0x0" }, { "file_name", file_name }, { "time_parsed", t_parsed }, { "time_checked", t_checked }}; return octave_value (m); } // User defined scripts. DEFINE_OV_TYPEID_FUNCTIONS_AND_DATA (octave_user_script, "user-defined script", "user-defined script"); octave_user_script::octave_user_script (void) : octave_user_code () { } octave_user_script::octave_user_script (const std::string& fnm, const std::string& nm, const octave::symbol_scope& scope, octave::tree_statement_list *cmds, const std::string& ds) : octave_user_code (fnm, nm, scope, cmds, ds) { if (cmd_list) cmd_list->mark_as_script_body (); } octave_user_script::octave_user_script (const std::string& fnm, const std::string& nm, const octave::symbol_scope& scope, const std::string& ds) : octave_user_code (fnm, nm, scope, nullptr, ds) { } // We must overload the call method so that we call the proper // push_stack_frame method, which is overloaded for pointers to // octave_function, octave_user_function, and octave_user_script // objects. octave_value_list octave_user_script::call (octave::tree_evaluator& tw, int nargout, const octave_value_list& args) { tw.push_stack_frame (this); octave::unwind_action act ([&tw] () { tw.pop_stack_frame (); }); return execute (tw, nargout, args); } octave_value_list octave_user_script::execute (octave::tree_evaluator& tw, int nargout, const octave_value_list& args) { return tw.execute_user_script (*this, nargout, args); } void octave_user_script::accept (octave::tree_walker& tw) { tw.visit_octave_user_script (*this); } // User defined functions. DEFINE_OV_TYPEID_FUNCTIONS_AND_DATA (octave_user_function, "user-defined function", "user-defined function"); // Ugh. This really needs to be simplified (code/data? // extrinsic/intrinsic state?). octave_user_function::octave_user_function (const octave::symbol_scope& scope, octave::tree_parameter_list *pl, octave::tree_parameter_list *rl, octave::tree_statement_list *cl) : octave_user_code ("", "", scope, cl, ""), param_list (pl), ret_list (rl), lead_comm (), trail_comm (), location_line (0), location_column (0), parent_name (), system_fcn_file (false), num_named_args (param_list ? param_list->length () : 0), subfunction (false), inline_function (false), anonymous_function (false), nested_function (false), class_constructor (none), class_method (none) #if defined (HAVE_LLVM) , jit_info (0) #endif { if (cmd_list) cmd_list->mark_as_function_body (); } octave_user_function::~octave_user_function (void) { delete param_list; delete ret_list; delete lead_comm; delete trail_comm; #if defined (HAVE_LLVM) delete jit_info; #endif } octave_user_function * octave_user_function::define_ret_list (octave::tree_parameter_list *t) { ret_list = t; return this; } // If there is no explicit end statement at the end of the function, // relocate the no_op that was generated for the end of file condition // to appear on the next line after the last statement in the file, or // the next line after the function keyword if there are no statements. // More precisely, the new location should probably be on the next line // after the end of the parameter list, but we aren't tracking that // information (yet). void octave_user_function::maybe_relocate_end_internal (void) { if (cmd_list && ! cmd_list->empty ()) { octave::tree_statement *last_stmt = cmd_list->back (); if (last_stmt && last_stmt->is_end_of_fcn_or_script () && last_stmt->is_end_of_file ()) { octave::tree_statement_list::reverse_iterator next_to_last_elt = cmd_list->rbegin (); next_to_last_elt++; int new_eof_line; int new_eof_col; if (next_to_last_elt == cmd_list->rend ()) { new_eof_line = beginning_line (); new_eof_col = beginning_column (); } else { octave::tree_statement *next_to_last_stmt = *next_to_last_elt; new_eof_line = next_to_last_stmt->line (); new_eof_col = next_to_last_stmt->column (); } last_stmt->set_location (new_eof_line + 1, new_eof_col); } } } void octave_user_function::maybe_relocate_end (void) { std::map<std::string, octave_value> fcns = subfunctions (); if (! fcns.empty ()) { for (auto& nm_fnval : fcns) { octave_user_function *f = nm_fnval.second.user_function_value (); if (f) f->maybe_relocate_end_internal (); } } maybe_relocate_end_internal (); } void octave_user_function::stash_parent_fcn_scope (const octave::symbol_scope& ps) { m_scope.set_parent (ps); } std::string octave_user_function::profiler_name (void) const { std::ostringstream result; if (is_anonymous_function ()) result << "anonymous@" << fcn_file_name () << ':' << location_line << ':' << location_column; else if (is_subfunction ()) result << parent_fcn_name () << '>' << name (); else if (is_class_method ()) result << '@' << dispatch_class () << '/' << name (); else if (is_class_constructor () || is_classdef_constructor ()) result << '@' << name (); else if (is_inline_function ()) result << "inline@" << fcn_file_name () << ':' << location_line << ':' << location_column; else result << name (); return result.str (); } void octave_user_function::mark_as_system_fcn_file (void) { if (! file_name.empty ()) { // We really should stash the whole path to the file we found, // when we looked it up, to avoid possible race conditions... // FIXME // // We probably also don't need to get the library directory // every time, but since this function is only called when the // function file is parsed, it probably doesn't matter that // much. std::string ff_name = octave::fcn_file_in_path (file_name); std::string fcn_file_dir = octave::config::fcn_file_dir (); if (fcn_file_dir == ff_name.substr (0, fcn_file_dir.length ())) system_fcn_file = true; } else system_fcn_file = false; } void octave_user_function::erase_subfunctions (void) { m_scope.erase_subfunctions (); } bool octave_user_function::takes_varargs (void) const { return (param_list && param_list->takes_varargs ()); } bool octave_user_function::takes_var_return (void) const { return (ret_list && ret_list->takes_varargs ()); } void octave_user_function::mark_as_private_function (const std::string& cname) { m_scope.mark_subfunctions_in_scope_as_private (cname); octave_function::mark_as_private_function (cname); } void octave_user_function::lock_subfunctions (void) { m_scope.lock_subfunctions (); } void octave_user_function::unlock_subfunctions (void) { m_scope.unlock_subfunctions (); } std::map<std::string, octave_value> octave_user_function::subfunctions (void) const { return m_scope.subfunctions (); } // Find definition of final subfunction in list of subfuns: // // sub1>sub2>...>subN octave_value octave_user_function::find_subfunction (const std::string& subfuns_arg) const { std::string subfuns = subfuns_arg; std::string first_fun = subfuns; size_t pos = subfuns.find ('>'); if (pos == std::string::npos) subfuns = ""; else { first_fun = subfuns.substr (0, pos-1); subfuns = subfuns.substr (pos+1); } octave_value ov_fcn = m_scope.find_subfunction (first_fun); if (subfuns.empty ()) return ov_fcn; octave_user_function *fcn = ov_fcn.user_function_value (); return fcn->find_subfunction (subfuns); } bool octave_user_function::has_subfunctions (void) const { return m_scope.has_subfunctions (); } void octave_user_function::stash_subfunction_names (const std::list<std::string>& names) { m_scope.stash_subfunction_names (names); } std::list<std::string> octave_user_function::subfunction_names (void) const { return m_scope.subfunction_names (); } octave_value_list octave_user_function::all_va_args (const octave_value_list& args) { octave_value_list retval; octave_idx_type n = args.length () - num_named_args; if (n > 0) retval = args.slice (num_named_args, n); return retval; } // We must overload the call method so that we call the proper // push_stack_frame method, which is overloaded for pointers to // octave_function, octave_user_function, and octave_user_script // objects. octave_value_list octave_user_function::call (octave::tree_evaluator& tw, int nargout, const octave_value_list& args) { tw.push_stack_frame (this); octave::unwind_action act ([&tw] () { tw.pop_stack_frame (); }); return execute (tw, nargout, args); } octave_value_list octave_user_function::execute (octave::tree_evaluator& tw, int nargout, const octave_value_list& args) { return tw.execute_user_function (*this, nargout, args); } void octave_user_function::accept (octave::tree_walker& tw) { tw.visit_octave_user_function (*this); } octave::tree_expression * octave_user_function::special_expr (void) { assert (is_special_expr ()); assert (cmd_list->length () == 1); octave::tree_statement *stmt = cmd_list->front (); return stmt->expression (); } bool octave_user_function::subsasgn_optimization_ok (void) { bool retval = false; if (Voptimize_subsasgn_calls && param_list && ret_list && param_list->length () > 0 && ! param_list->varargs_only () && ret_list->length () == 1 && ! ret_list->takes_varargs ()) { octave::tree_identifier *par1 = param_list->front ()->ident (); octave::tree_identifier *ret1 = ret_list->front ()->ident (); retval = par1->name () == ret1->name (); } return retval; } std::string octave_user_function::ctor_type_str (void) const { std::string retval; switch (class_constructor) { case none: retval = "none"; break; case legacy: retval = "legacy"; break; case classdef: retval = "classdef"; break; default: retval = "unrecognized enum value"; break; } return retval; } std::string octave_user_function::method_type_str (void) const { std::string retval; switch (class_method) { case none: retval = "none"; break; case legacy: retval = "legacy"; break; case classdef: retval = "classdef"; break; default: retval = "unrecognized enum value"; break; } return retval; } octave_value octave_user_function::dump (void) const { std::map<std::string, octave_value> m = {{ "user_code", octave_user_code::dump () }, { "line", location_line }, { "col", location_column }, { "end_line", end_location_line }, { "end_col", end_location_column }, { "parent_name", parent_name }, { "system_fcn_file", system_fcn_file }, { "num_named_args", num_named_args }, { "subfunction", subfunction }, { "inline_function", inline_function }, { "anonymous_function", anonymous_function }, { "nested_function", nested_function }, { "ctor_type", ctor_type_str () }, { "class_method", class_method }}; return octave_value (m); } void octave_user_function::print_code_function_header (const std::string& prefix) { octave::tree_print_code tpc (octave_stdout, prefix); tpc.visit_octave_user_function_header (*this); } void octave_user_function::print_code_function_trailer (const std::string& prefix) { octave::tree_print_code tpc (octave_stdout, prefix); tpc.visit_octave_user_function_trailer (*this); } void octave_user_function::restore_warning_states (void) { octave::interpreter& interp = octave::__get_interpreter__ ("octave_user_function::restore_warning_states"); octave::tree_evaluator& tw = interp.get_evaluator (); octave_value val = tw.get_auto_fcn_var (octave::stack_frame::SAVED_WARNING_STATES); if (val.is_defined ()) { // Fail spectacularly if SAVED_WARNING_STATES is not an // octave_map (or octave_scalar_map) object. if (! val.isstruct ()) panic_impossible (); octave_map m = val.map_value (); Cell ids = m.contents ("identifier"); Cell states = m.contents ("state"); for (octave_idx_type i = 0; i < m.numel (); i++) Fwarning (interp, ovl (states(i), ids(i))); } } DEFMETHOD (nargin, interp, args, , doc: /* -*- texinfo -*- @deftypefn {} {} nargin () @deftypefnx {} {} nargin (@var{fcn}) Report the number of input arguments to a function. Called from within a function, return the number of arguments passed to the function. At the top level, return the number of command line arguments passed to Octave. If called with the optional argument @var{fcn}---a function name or handle---return the declared number of arguments that the function can accept. If the last argument to @var{fcn} is @var{varargin} the returned value is negative. For example, the function @code{union} for sets is declared as @example @group function [y, ia, ib] = union (a, b, varargin) and nargin ("union") @result{} -3 @end group @end example Programming Note: @code{nargin} does not work on compiled functions (@file{.oct} files) such as built-in or dynamically loaded functions. @seealso{nargout, narginchk, varargin, inputname} @end deftypefn */) { int nargin = args.length (); if (nargin > 1) print_usage (); octave_value retval; if (nargin == 1) { octave_value func = args(0); if (func.is_string ()) { octave::symbol_table& symtab = interp.get_symbol_table (); std::string name = func.string_value (); func = symtab.find_function (name); if (func.is_undefined ()) error ("nargin: invalid function name: %s", name.c_str ()); } octave_function *fcn_val = func.function_value (true); if (! fcn_val) error ("nargin: FCN must be a string or function handle"); octave_user_function *fcn = fcn_val->user_function_value (true); if (! fcn) { // Matlab gives up for histc, so maybe it's ok that we // give up sometimes too? std::string type = fcn_val->type_name (); error ("nargin: number of input arguments unavailable for %s objects", type.c_str ()); } octave::tree_parameter_list *param_list = fcn->parameter_list (); retval = (param_list ? param_list->length () : 0); if (fcn->takes_varargs ()) retval = -1 - retval; } else { octave::tree_evaluator& tw = interp.get_evaluator (); retval = tw.get_auto_fcn_var (octave::stack_frame::NARGIN); if (retval.is_undefined ()) retval = 0; } return retval; } DEFMETHOD (nargout, interp,args, , doc: /* -*- texinfo -*- @deftypefn {} {} nargout () @deftypefnx {} {} nargout (@var{fcn}) Report the number of output arguments from a function. Called from within a function, return the number of values the caller expects to receive. At the top level, @code{nargout} with no argument is undefined and will produce an error. If called with the optional argument @var{fcn}---a function name or handle---return the number of declared output values that the function can produce. If the final output argument is @var{varargout} the returned value is negative. For example, @example f () @end example @noindent will cause @code{nargout} to return 0 inside the function @code{f} and @example [s, t] = f () @end example @noindent will cause @code{nargout} to return 2 inside the function @code{f}. In the second usage, @example nargout (@@histc) # or nargout ("histc") using a string input @end example @noindent will return 2, because @code{histc} has two outputs, whereas @example nargout (@@imread) @end example @noindent will return -2, because @code{imread} has two outputs and the second is @var{varargout}. Programming Note. @code{nargout} does not work for built-in functions and returns -1 for all anonymous functions. @seealso{nargin, varargout, isargout, nthargout} @end deftypefn */) { int nargin = args.length (); if (nargin > 1) print_usage (); octave_value retval; if (nargin == 1) { octave_value func = args(0); if (func.is_string ()) { octave::symbol_table& symtab = interp.get_symbol_table (); std::string name = func.string_value (); func = symtab.find_function (name); if (func.is_undefined ()) error ("nargout: invalid function name: %s", name.c_str ()); } if (func.is_inline_function ()) return ovl (1); if (func.is_function_handle ()) { octave_fcn_handle *fh = func.fcn_handle_value (); if (fh->is_anonymous ()) return ovl (-1); } octave_function *fcn_val = func.function_value (true); if (! fcn_val) error ("nargout: FCN must be a string or function handle"); octave_user_function *fcn = fcn_val->user_function_value (true); if (! fcn) { // Matlab gives up for histc, so maybe it's ok that we // give up sometimes too? std::string type = fcn_val->type_name (); error ("nargout: number of output arguments unavailable for %s objects", type.c_str ()); } octave::tree_parameter_list *ret_list = fcn->return_list (); retval = (ret_list ? ret_list->length () : 0); if (fcn->takes_var_return ()) retval = -1 - retval; } else { if (interp.at_top_level ()) error ("nargout: invalid call at top level"); octave::tree_evaluator& tw = interp.get_evaluator (); retval = tw.get_auto_fcn_var (octave::stack_frame::NARGOUT); if (retval.is_undefined ()) retval = 0; } return retval; } DEFUN (optimize_subsasgn_calls, args, nargout, doc: /* -*- texinfo -*- @deftypefn {} {@var{val} =} optimize_subsasgn_calls () @deftypefnx {} {@var{old_val} =} optimize_subsasgn_calls (@var{new_val}) @deftypefnx {} {} optimize_subsasgn_calls (@var{new_val}, "local") Query or set the internal flag for @code{subsasgn} method call optimizations. If true, Octave will attempt to eliminate the redundant copying when calling the @code{subsasgn} method of a user-defined class. When called from inside a function with the @qcode{"local"} option, the variable is changed locally for the function and any subroutines it calls. The original variable value is restored when exiting the function. @seealso{subsasgn} @end deftypefn */) { return SET_INTERNAL_VARIABLE (optimize_subsasgn_calls); } static bool val_in_table (const Matrix& table, double val) { if (table.isempty ()) return false; octave_idx_type i = table.lookup (val, ASCENDING); return (i > 0 && table(i-1) == val); } static bool isargout1 (int nargout, const Matrix& ignored, double k) { if (k != octave::math::fix (k) || k <= 0) error ("isargout: K must be a positive integer"); return (k == 1 || k <= nargout) && ! val_in_table (ignored, k); } DEFMETHOD (isargout, interp, args, , doc: /* -*- texinfo -*- @deftypefn {} {} isargout (@var{k}) Within a function, return a logical value indicating whether the argument @var{k} will be assigned to a variable on output. If the result is false, the argument has been ignored during the function call through the use of the tilde (~) special output argument. Functions can use @code{isargout} to avoid performing unnecessary calculations for outputs which are unwanted. If @var{k} is outside the range @code{1:max (nargout)}, the function returns false. @var{k} can also be an array, in which case the function works element-by-element and a logical array is returned. At the top level, @code{isargout} returns an error. @seealso{nargout, varargout, nthargout} @end deftypefn */) { if (args.length () != 1) print_usage (); if (interp.at_top_level ()) error ("isargout: invalid call at top level"); octave::tree_evaluator& tw = interp.get_evaluator (); octave_value tmp; int nargout1 = 0; tmp = tw.get_auto_fcn_var (octave::stack_frame::NARGOUT); if (tmp.is_defined ()) nargout1 = tmp.int_value (); Matrix ignored; tmp = tw.get_auto_fcn_var (octave::stack_frame::IGNORED); if (tmp.is_defined ()) ignored = tmp.matrix_value (); if (args(0).is_scalar_type ()) { double k = args(0).double_value (); return ovl (isargout1 (nargout1, ignored, k)); } else if (args(0).isnumeric ()) { const NDArray ka = args(0).array_value (); boolNDArray r (ka.dims ()); for (octave_idx_type i = 0; i < ka.numel (); i++) r(i) = isargout1 (nargout1, ignored, ka(i)); return ovl (r); } else err_wrong_type_arg ("isargout", args(0)); return ovl (); } /* %!function [x, y] = try_isargout () %! if (isargout (1)) %! if (isargout (2)) %! x = 1; y = 2; %! else %! x = -1; %! endif %! else %! if (isargout (2)) %! y = -2; %! else %! error ("no outputs requested"); %! endif %! endif %!endfunction %! %!function [a, b] = try_isargout2 (x, y) %! a = y; %! b = {isargout(1), isargout(2), x}; %!endfunction %! %!test %! [x, y] = try_isargout (); %! assert ([x, y], [1, 2]); %! %!test %! [x, ~] = try_isargout (); %! assert (x, -1); %! %!test %! [~, y] = try_isargout (); %! assert (y, -2); %! %!error [~, ~] = try_isargout () %! ## Check to see that isargout isn't sticky: %!test %! [x, y] = try_isargout (); %! assert ([x, y], [1, 2]); %! ## It should work without (): %!test %! [~, y] = try_isargout; %! assert (y, -2); %! ## It should work in function handles, anonymous functions, and cell ## arrays of handles or anonymous functions. %!test %! fh = @try_isargout; %! af = @() try_isargout; %! c = {fh, af}; %! [~, y] = fh (); %! assert (y, -2); %! [~, y] = af (); %! assert (y, -2); %! [~, y] = c{1}(); %! assert (y, -2); %! [~, y] = c{2}(); %! assert (y, -2); %! ## Nesting, anyone? %!test %! [~, b] = try_isargout2 (try_isargout, rand); %! assert (b, {0, 1, -1}); %!test %! [~, b] = try_isargout2 ({try_isargout, try_isargout}, rand); %! assert (b, {0, 1, {-1, -1}}); */