changeset 26828:6e9034836239

allow handles to nested functions to work (bug #39257) * stack-frame.h (stack_frame::set_closure_links): New function. (usr_fcn_stack_frame::usr_fcn_stack_frame): New optional argument, ACCESS_LINK, to allow access link to be set directly when creating stack frames for handles to nested functions. * call-stack.h, call-stack.cc (call_stack::push): New argument, CLOSURE_FRAMES, in method to push user function objects on the call stack to allow call stack context for handles to nested functions to be set. * symscope.h (symbol_scope::symbol_scope_rep::is_parent, symbol_scope::is_parent): New functions. * ov-fcn-handle.h, ov-fcn-handle.cc (octave_fcn_handle::m_closure_frames, octave_fcn_handle::m_is_nested): New data members. (octave_fcn_handle::is_nested): New function. (octave_fcn_handle::~octave_fcn_handle): Delete saved closure frames. (octave_fcn_handle::octave_fcn_handle): Mark handles to nested functions as nested instead of throwing error. (octave_fcn_handle::push_closure_context, octave_fcn_handle::workspace): New functions. (Ffunctions): Handle nested functions. (octave_fcn_handle::call): Pass pointer to first element of closure_frames list to feval when executing a function. * ov-fcn.h, ov-fcn.cc (octave_function::call): New overload with pointer to closure_context stack frames as argument. Provide default implementation. * ov-usr-fcn.h, ov-usr-fcn.cc (octave_user_function::parent_function): New member variable. (octave_user_function::is_parent_function): New function. (octave_user_function::call): Primary definition is now overload that includes closure context. The other form now forwards to the new version. * pt-eval.cc (tree_evaluator::execute_user_function): New arg, CLOSURE_FRAMES. Pass them to the call stack along with the user function. Push current stack frame to any function handles returned from a nested function or the parent of a nested function. * test/nest/nst1.m, test/nest/nst2.m, test/nest/nst3.m: New files. * test/nest/nest.tst: New tests.
author John W. Eaton <jwe@octave.org>
date Sun, 03 Mar 2019 10:19:36 +0000
parents d9770844392e
children 20881d195d20
files libinterp/corefcn/call-stack.cc libinterp/corefcn/call-stack.h libinterp/corefcn/stack-frame.h libinterp/corefcn/symscope.h libinterp/octave-value/ov-fcn-handle.cc libinterp/octave-value/ov-fcn-handle.h libinterp/octave-value/ov-fcn.cc libinterp/octave-value/ov-fcn.h libinterp/octave-value/ov-usr-fcn.cc libinterp/octave-value/ov-usr-fcn.h libinterp/parse-tree/pt-eval.cc libinterp/parse-tree/pt-eval.h test/nest/nest.tst test/nest/nst1.m test/nest/nst2.m test/nest/nst3.m
diffstat 16 files changed, 353 insertions(+), 53 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/corefcn/call-stack.cc	Sun Mar 03 07:08:52 2019 +0000
+++ b/libinterp/corefcn/call-stack.cc	Sun Mar 03 10:19:36 2019 +0000
@@ -437,7 +437,8 @@
     m_cs.push_back (new scope_stack_frame (*this, prev_frame, scope, slink));
   }
 
-  void call_stack::push (octave_user_function *fcn, unwind_protect *up_frame)
+  void call_stack::push (octave_user_function *fcn, unwind_protect *up_frame,
+                         stack_frame *closure_frames)
   {
     size_t prev_frame = m_curr_frame;
     m_curr_frame = m_cs.size ();
@@ -449,7 +450,8 @@
     stack_frame *slink = get_static_link (prev_frame);
 
     m_cs.push_back (new user_fcn_stack_frame (*this, fcn, up_frame,
-                                              prev_frame, slink));
+                                              prev_frame, slink,
+                                              closure_frames));
   }
 
   void call_stack::push (octave_user_script *script, unwind_protect *up_frame)
--- a/libinterp/corefcn/call-stack.h	Sun Mar 03 07:08:52 2019 +0000
+++ b/libinterp/corefcn/call-stack.h	Sun Mar 03 10:19:36 2019 +0000
@@ -165,7 +165,8 @@
 
     void push (const symbol_scope& scope);
 
-    void push (octave_user_function *fcn, unwind_protect *up_frame);
+    void push (octave_user_function *fcn, unwind_protect *up_frame,
+               stack_frame *closure_frames = nullptr);
 
     void push (octave_user_script *script, unwind_protect *up_frame);
 
--- a/libinterp/corefcn/stack-frame.h	Sun Mar 03 07:08:52 2019 +0000
+++ b/libinterp/corefcn/stack-frame.h	Sun Mar 03 10:19:36 2019 +0000
@@ -309,6 +309,12 @@
 
     stack_frame * access_link (void) const {return m_access_link; }
 
+    void set_closure_links (stack_frame *dup_frame)
+    {
+      m_static_link = dup_frame;
+      m_access_link = dup_frame;
+    }
+
     virtual size_t size (void) const;
 
     virtual void resize (size_t);
@@ -888,9 +894,12 @@
 
     user_fcn_stack_frame (call_stack& cs, octave_user_function *fcn,
                           unwind_protect *up_frame, size_t prev,
-                          stack_frame *static_link)
+                          stack_frame *static_link,
+                          stack_frame *access_link = nullptr)
       : base_value_stack_frame (cs, get_num_symbols (fcn), prev, static_link,
-                                get_access_link (fcn, static_link)),
+                                (access_link
+                                 ? access_link
+                                 : get_access_link (fcn, static_link))),
         m_fcn (fcn), m_unwind_protect_frame (up_frame)
     { }
 
--- a/libinterp/corefcn/symscope.h	Sun Mar 03 07:08:52 2019 +0000
+++ b/libinterp/corefcn/symscope.h	Sun Mar 03 10:19:36 2019 +0000
@@ -96,6 +96,8 @@
 
     void set_nesting_depth (size_t depth) { m_nesting_depth = depth; }
 
+    bool is_parent (void) const { return ! m_children.empty (); }
+
     bool is_static (void) const { return m_is_static; }
 
     void mark_static (void) { m_is_static = true; }
@@ -366,6 +368,11 @@
       return m_rep ? m_rep->is_nested () : false;
     }
 
+    bool is_parent (void) const
+    {
+      return m_rep ? m_rep->is_parent () : false;
+    }
+
     void set_nesting_depth (size_t depth)
     {
       if (m_rep)
--- a/libinterp/octave-value/ov-fcn-handle.cc	Sun Mar 03 07:08:52 2019 +0000
+++ b/libinterp/octave-value/ov-fcn-handle.cc	Sun Mar 03 10:19:36 2019 +0000
@@ -30,6 +30,7 @@
 #include <list>
 #include <ostream>
 #include <sstream>
+#include <string>
 #include <vector>
 
 #include "file-ops.h"
@@ -62,6 +63,7 @@
 #include "pt-misc.h"
 #include "pt-pr-code.h"
 #include "pt-stmt.h"
+#include "syminfo.h"
 #include "symscope.h"
 #include "unwind-prot.h"
 #include "variables.h"
@@ -82,7 +84,8 @@
 
 octave_fcn_handle::octave_fcn_handle (const octave_value& f,
                                       const std::string& n)
-  : fcn (f), nm (n), has_overloads (false)
+  : fcn (f), nm (n), has_overloads (false), overloads (),
+    m_is_nested (false), m_closure_frames (nullptr)
 {
   octave_user_function *uf = fcn.user_function_value (true);
 
@@ -95,7 +98,24 @@
     }
 
   if (uf && uf->is_nested_function () && ! uf->is_subfunction ())
-    error ("handles to nested functions are not yet supported");
+    m_is_nested = true;
+}
+
+octave_fcn_handle::~octave_fcn_handle (void)
+{
+  if (m_closure_frames)
+    {
+      while (m_closure_frames->size () > 0)
+        {
+          octave::stack_frame *elt = m_closure_frames->back ();
+
+          delete elt;
+
+          m_closure_frames->pop_back ();
+        }
+
+      delete m_closure_frames;
+    }
 }
 
 octave_value_list
@@ -140,15 +160,13 @@
 octave_value_list
 octave_fcn_handle::call (int nargout, const octave_value_list& args)
 {
-  octave_value_list retval;
+  octave::out_of_date_check (fcn, "", false);
 
-  octave::out_of_date_check (fcn, "", false);
+  // Possibly overloaded function.
+  octave_value fcn_to_call = fcn;
 
   if (has_overloads)
     {
-      // Possibly overloaded function.
-      octave_value ov_fcn;
-
       // Compute dispatch type.
       builtin_type_t btyp;
       std::string dispatch_type = octave::get_dispatch_type (args, btyp);
@@ -157,7 +175,7 @@
       if (btyp != btyp_unknown)
         {
           octave::out_of_date_check (builtin_overloads[btyp], dispatch_type, false);
-          ov_fcn = builtin_overloads[btyp];
+          fcn_to_call = builtin_overloads[btyp];
         }
       else
         {
@@ -188,7 +206,7 @@
                       set_overload (pname, ftmp);
 
                       octave::out_of_date_check (ftmp, pname, false);
-                      ov_fcn = ftmp;
+                      fcn_to_call = ftmp;
 
                       break;
                     }
@@ -199,28 +217,28 @@
           else
             {
               octave::out_of_date_check (it->second, dispatch_type, false);
-              ov_fcn = it->second;
+              fcn_to_call = it->second;
             }
         }
 
-      if (ov_fcn.is_defined ())
-        retval = octave::feval (ov_fcn, args, nargout);
-      else if (fcn.is_defined ())
-        retval = octave::feval (fcn, args, nargout);
-      else
+      if (! fcn_to_call.is_defined ())
         error ("%s: no method for class %s",
                nm.c_str (), dispatch_type.c_str ());
     }
-  else
-    {
-      // Non-overloaded function (anonymous, subfunction, private function).
-      if (fcn.is_defined ())
-        retval = octave::feval (fcn, args, nargout);
-      else
-        error ("%s: no longer valid function handle", nm.c_str ());
-    }
+  else if (! fcn_to_call.is_defined ())
+    error ("%s: no longer valid function handle", nm.c_str ());
+
+  octave::stack_frame *closure_context = nullptr;
 
-  return retval;
+  if (m_closure_frames && m_closure_frames->size () > 0)
+    closure_context = m_closure_frames->front ();
+
+  octave::tree_evaluator& tw
+    = octave::__get_evaluator__ ("octave_fcn_handle::call");
+
+  octave_function *of = fcn_to_call.function_value ();
+
+  return of->call (tw, nargout, args, closure_context);
 }
 
 dim_vector
@@ -230,6 +248,83 @@
   return dv;
 }
 
+// Save call stack frames for handles to nested functions.
+
+void
+octave_fcn_handle::push_closure_context (octave::tree_evaluator& tw)
+{
+  if (! m_closure_frames)
+    m_closure_frames = new std::list<octave::stack_frame *> ();
+
+  octave::call_stack& main_cs = tw.get_call_stack ();
+
+  octave::stack_frame& curr_frame = main_cs.get_current_stack_frame ();
+
+  octave::stack_frame *dup_frame = curr_frame.dup ();
+
+  if (! m_closure_frames->empty ())
+    {
+      octave::stack_frame *top_frame = m_closure_frames->back ();
+
+      // Arrange for static and access links in the top stack frame (the
+      // last one saved before this one) to point to the new duplicated
+      // frame.  This way we will look up through the duplicated frames
+      // when evaluating the function.
+
+      top_frame->set_closure_links (dup_frame);
+    }
+
+  m_closure_frames->push_back (dup_frame);
+}
+
+octave_value
+octave_fcn_handle::workspace (void) const
+{
+  if (nm == anonymous)
+    {
+      octave_user_function *fu = fcn.user_function_value ();
+
+      octave_scalar_map ws;
+
+      if (fu)
+        {
+          for (const auto& nm_val : fu->local_var_init_vals ())
+            ws.assign (nm_val.first, nm_val.second);
+        }
+
+      return ws;
+    }
+  else if (m_closure_frames)
+    {
+      octave_idx_type num_frames = m_closure_frames->size ();
+
+      Cell ws_frames (num_frames, 1);
+
+      octave_idx_type i = 0;
+
+      for (auto elt : *m_closure_frames)
+        {
+          octave::symbol_info_list symbols = elt->all_variables ();
+
+          octave_scalar_map ws;
+
+          for (auto sym_name : symbols.names ())
+            {
+              octave_value val = symbols.varval (sym_name);
+
+              if (val.is_defined ())
+                ws.assign (sym_name, val);
+            }
+
+          ws_frames(i++) = ws;
+        }
+
+      return ws_frames;
+    }
+
+  return Cell ();
+}
+
 bool
 octave_fcn_handle::is_equal_to (const octave_fcn_handle& h) const
 {
@@ -1704,6 +1799,9 @@
 The function is a subfunction within an m-file.
 @end table
 
+@item nested
+The function is nested.
+
 @item file
 The m-file that will be called to perform the function.  This field is empty
 for anonymous and built-in functions.
@@ -1755,6 +1853,8 @@
         }
       else if (fcn->is_private_function ())
         m.setfield ("type", "private");
+      else if (fh->is_nested ())
+        m.setfield ("type", "nested");
       else if (fh->is_overloaded ())
         m.setfield ("type", "overloaded");
       else
@@ -1767,18 +1867,14 @@
     {
       m.setfield ("file", nm);
 
-      octave_user_function *fu = fh->user_function_value ();
-
-      octave_scalar_map ws;
-      for (const auto& nm_val : fu->local_var_init_vals ())
-        ws.assign (nm_val.first, nm_val.second);
-
-      m.setfield ("workspace", ws);
+      m.setfield ("workspace", fh->workspace ());
     }
   else if (fcn->is_user_function () || fcn->is_user_script ())
     {
       octave_function *fu = fh->function_value ();
       m.setfield ("file", fu->fcn_file_name ());
+
+      m.setfield ("workspace", fh->workspace ());
     }
   else
     m.setfield ("file", "");
--- a/libinterp/octave-value/ov-fcn-handle.h	Sun Mar 03 07:08:52 2019 +0000
+++ b/libinterp/octave-value/ov-fcn-handle.h	Sun Mar 03 10:19:36 2019 +0000
@@ -27,6 +27,7 @@
 #include "octave-config.h"
 
 #include <iosfwd>
+#include <list>
 #include <string>
 
 #include "ov-base.h"
@@ -36,6 +37,8 @@
 namespace octave
 {
   class interpreter;
+  class stack_frame;
+  class tree_evaluator;
 }
 
 // Function handles.
@@ -53,16 +56,21 @@
   static const std::string anonymous;
 
   octave_fcn_handle (void)
-    : fcn (), nm (), has_overloads (false), overloads () { }
+    : fcn (), nm (), has_overloads (false), overloads (),
+      m_is_nested (false), m_closure_frames (nullptr)
+  { }
 
   octave_fcn_handle (const std::string& n)
-    : fcn (), nm (n), has_overloads (false), overloads () { }
+    : fcn (), nm (n), has_overloads (false), overloads (),
+      m_is_nested (false), m_closure_frames (nullptr)
+  { }
 
   octave_fcn_handle (const octave_value& f,  const std::string& n = anonymous);
 
   octave_fcn_handle (const octave_fcn_handle& fh)
     : octave_base_value (fh), fcn (fh.fcn), nm (fh.nm),
-      has_overloads (fh.has_overloads), overloads ()
+      has_overloads (fh.has_overloads), overloads (),
+      m_is_nested (fh.m_is_nested), m_closure_frames (fh.m_closure_frames)
   {
     for (int i = 0; i < btyp_num_types; i++)
       builtin_overloads[i] = fh.builtin_overloads[i];
@@ -70,7 +78,7 @@
     overloads = fh.overloads;
   }
 
-  ~octave_fcn_handle (void) = default;
+  ~octave_fcn_handle (void);
 
   octave_base_value * clone (void) const
   { return new octave_fcn_handle (*this); }
@@ -96,13 +104,19 @@
 
   bool is_overloaded (void) const { return has_overloads; }
 
+  bool is_nested (void) const { return m_is_nested; }
+
   dim_vector dims (void) const;
 
   octave_function * function_value (bool = false)
-  { return fcn.function_value (); }
+  {
+    return fcn.function_value ();
+  }
 
   octave_user_function * user_function_value (bool = false)
-  { return fcn.user_function_value (); }
+  {
+    return fcn.user_function_value ();
+  }
 
   octave_fcn_handle * fcn_handle_value (bool = false) { return this; }
 
@@ -110,6 +124,10 @@
 
   std::string fcn_name (void) const { return nm; }
 
+  void push_closure_context (octave::tree_evaluator& tw);
+
+  octave_value workspace (void) const;
+
   void set_overload (builtin_type_t btyp, const octave_value& ov_fcn)
   {
     if (btyp != btyp_unknown)
@@ -174,6 +192,14 @@
   // Overloads for other classes.
   str_ov_map overloads;
 
+  // TRUE means this is a handle to a nested function.
+  bool m_is_nested;
+
+  // Saved stack frames for handles to nested functions.  This allows us
+  // to access non-locals and other context info when calling nested
+  // functions indirectly through handles.
+  std::list<octave::stack_frame *> *m_closure_frames;
+
   bool parse_anon_fcn_handle (const std::string& fcn_text);
 
   virtual octave_value_list call (int nargout, const octave_value_list& args);
--- a/libinterp/octave-value/ov-fcn.cc	Sun Mar 03 07:08:52 2019 +0000
+++ b/libinterp/octave-value/ov-fcn.cc	Sun Mar 03 10:19:36 2019 +0000
@@ -33,12 +33,21 @@
 octave_function::clone (void) const
 {
   panic_impossible ();
-  return nullptr;
 }
 
 octave_base_value *
 octave_function::empty_clone (void) const
 {
   panic_impossible ();
-  return nullptr;
 }
+
+octave_value_list
+octave_function::call (octave::tree_evaluator& tw, int nargout,
+                       const octave_value_list& args,
+                       octave::stack_frame *closure_context)
+{
+  if (closure_context)
+    panic_impossible ();
+
+  return call (tw, nargout, args);
+}
--- a/libinterp/octave-value/ov-fcn.h	Sun Mar 03 07:08:52 2019 +0000
+++ b/libinterp/octave-value/ov-fcn.h	Sun Mar 03 10:19:36 2019 +0000
@@ -37,6 +37,7 @@
 
 namespace octave
 {
+  class stack_frame;
   class tree_evaluator;
   class tree_walker;
 }
@@ -220,7 +221,12 @@
   call (octave::tree_evaluator& tw, int nargout = 0,
         const octave_value_list& args = octave_value_list ()) = 0;
 
-protected:
+  virtual octave_value_list
+  call (octave::tree_evaluator& tw, int nargout,
+        const octave_value_list& args,
+        octave::stack_frame *closure_context);
+
+ protected:
 
   octave_function (const std::string& nm,
                    const std::string& ds = "")
--- a/libinterp/octave-value/ov-usr-fcn.cc	Sun Mar 03 07:08:52 2019 +0000
+++ b/libinterp/octave-value/ov-usr-fcn.cc	Sun Mar 03 10:19:36 2019 +0000
@@ -451,9 +451,10 @@
 
 octave_value_list
 octave_user_function::call (octave::tree_evaluator& tw, int nargout,
-                            const octave_value_list& args)
+                            const octave_value_list& args,
+                            octave::stack_frame *closure_frames)
 {
-  return tw.execute_user_function (*this, nargout, args);
+  return tw.execute_user_function (*this, nargout, args, closure_frames);
 }
 
 void
--- a/libinterp/octave-value/ov-usr-fcn.h	Sun Mar 03 07:08:52 2019 +0000
+++ b/libinterp/octave-value/ov-usr-fcn.h	Sun Mar 03 10:19:36 2019 +0000
@@ -327,6 +327,8 @@
 
   bool is_nested_function (void) const { return nested_function; }
 
+  bool is_parent_function (void) const { return m_scope.is_parent (); }
+
   void mark_as_legacy_constructor (void) { class_constructor = legacy; }
 
   bool is_legacy_constructor (const std::string& cname = "") const
@@ -361,7 +363,14 @@
 
   octave_value_list
   call (octave::tree_evaluator& tw, int nargout = 0,
-        const octave_value_list& args = octave_value_list ());
+        const octave_value_list& args = octave_value_list ())
+  {
+    return call (tw, nargout, args, nullptr);
+  }
+
+  octave_value_list
+  call (octave::tree_evaluator& tw, int nargout,
+        const octave_value_list& args, octave::stack_frame *);
 
   octave::tree_parameter_list * parameter_list (void) { return param_list; }
 
@@ -446,9 +455,12 @@
   // TRUE means this is an anonymous function.
   bool anonymous_function;
 
-  // TRUE means this is a nested function. (either a child or parent)
+  // TRUE means this is a nested function.
   bool nested_function;
 
+  // TRUE means this function contains a nested function.
+  bool parent_function;
+
   // Enum describing whether this function is the constructor for class object.
   class_method_type class_constructor;
 
--- a/libinterp/parse-tree/pt-eval.cc	Sun Mar 03 07:08:52 2019 +0000
+++ b/libinterp/parse-tree/pt-eval.cc	Sun Mar 03 10:19:36 2019 +0000
@@ -1763,7 +1763,8 @@
   octave_value_list
   tree_evaluator::execute_user_function (octave_user_function& user_function,
                                          int nargout,
-                                         const octave_value_list& xargs)
+                                         const octave_value_list& xargs,
+                                         stack_frame *closure_frames)
   {
     octave_value_list retval;
 
@@ -1803,7 +1804,10 @@
     // Save old and set current symbol table context, for
     // eval_undefined_error().
 
-    m_call_stack.push (&user_function, &frame);
+    //    std::cerr << "eval: " << user_function.name ()
+    //              << " with closure_frames: " << closure_frames << std::endl;
+
+    m_call_stack.push (&user_function, &frame, closure_frames);
 
     frame.protect_var (Vtrack_line_num);
     // update source line numbers, even if debugging
@@ -1913,6 +1917,28 @@
                                                       varargout);
       }
 
+    if (user_function.is_nested_function ()
+        || user_function.is_parent_function ())
+      {
+        // Copy current stack frame to handles to nested functions.
+
+        for (octave_idx_type i = 0; i < retval.length (); i++)
+          {
+            octave_value val = retval(i);
+
+            if (val.is_function_handle ())
+              {
+                octave_fcn_handle *fh = val.fcn_handle_value ();
+
+                if (fh && fh->is_nested ())
+                  {
+                    // std::cerr << "pushing closure context" << std::endl;
+                    fh->push_closure_context (*this);
+                  }
+              }
+          }
+      }
+
     return retval;
   }
 
--- a/libinterp/parse-tree/pt-eval.h	Sun Mar 03 07:08:52 2019 +0000
+++ b/libinterp/parse-tree/pt-eval.h	Sun Mar 03 10:19:36 2019 +0000
@@ -200,8 +200,9 @@
     void visit_octave_user_function (octave_user_function&);
 
     octave_value_list
-    execute_user_function (octave_user_function& user_function, int nargout,
-                           const octave_value_list& args);
+    execute_user_function (octave_user_function& user_function,
+                           int nargout, const octave_value_list& args,
+                           stack_frame *closure_frames = nullptr);
 
     void visit_octave_user_function_header (octave_user_function&);
 
--- a/test/nest/nest.tst	Sun Mar 03 07:08:52 2019 +0000
+++ b/test/nest/nest.tst	Sun Mar 03 10:19:36 2019 +0000
@@ -70,3 +70,69 @@
 %!error <D' undefined near line 7> scope2
 %!error <can not add variable "y" to a static workspace> nest_eval ("y = 5;", "")
 %!error <can not add variable "y" to a static workspace> nest_eval ("y;", "")
+
+## Test the way that non-local variables referenced by nested functions
+## work with function handles.
+
+## FH1 and FH2 were created separately so will have distinct
+## closure contexts.handles, FH3 is a copy of FH2 so they will
+## share the same context.
+
+%!test <39257>
+%! fh1 = nst1 (13);
+%! fh2 = nst1 (13);
+%! fh3 = fh2;
+%!
+%! assert (fh1 (), 13);
+%! assert (fh2 (), 13);
+%! assert (fh3 (), 13);
+%!
+%! assert (fh1 (42), 42);
+%! assert (fh2 (), 13);
+%! assert (fh3 (), 13);
+%!
+%! assert (fh2 (pi), pi);
+%! assert (fh1 (), 42);
+%! assert (fh3 (), pi);
+
+## Similar to the test above, but with persistent variables.  These are
+## stored in the function, not the closure context, so are shared among
+## all handles whether they are created separately or copied.
+
+%!test
+%! fh1 = nst2 (13);
+%! fh2 = nst2 (13);
+%! fh3 = fh2;
+%!
+%! assert (fh1 (), 13);
+%! assert (fh2 (), 13);
+%! assert (fh3 (), 13);
+%!
+%! assert (fh1 (42), 42);
+%! assert (fh2 (), 42);
+%! assert (fh3 (), 42);
+%!
+%! assert (fh2 (pi), pi);
+%! assert (fh1 (), pi);
+%! assert (fh3 (), pi);
+
+## And again with global variables.
+
+%!test
+%! fh1 = nst3 (13);
+%! fh2 = nst3 (13);
+%! fh3 = fh2;
+%!
+%! assert (fh1 (), 13);
+%! assert (fh2 (), 13);
+%! assert (fh3 (), 13);
+%!
+%! assert (fh1 (42), 42);
+%! assert (fh2 (), 42);
+%! assert (fh3 (), 42);
+%!
+%! assert (fh2 (pi), pi);
+%! assert (fh1 (), pi);
+%! assert (fh3 (), pi);
+%!
+%! clear -global g;  # cleanup after tests
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/nest/nst1.m	Sun Mar 03 10:19:36 2019 +0000
@@ -0,0 +1,12 @@
+function fh = nst1 (xval)
+  if (nargin > 0)
+    x = xval;
+  end
+  fh = @nst;
+  function r = nst (xval)
+    if (nargin > 0)
+      x = xval;
+    end
+    r = x;
+  end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/nest/nst2.m	Sun Mar 03 10:19:36 2019 +0000
@@ -0,0 +1,13 @@
+function fh = nst2 (pval)
+  persistent p
+  if (nargin > 0)
+    p = pval;
+  end
+  fh = @nst;
+  function r = nst (pval)
+    if (nargin > 0)
+      p = pval;
+    end
+    r = p;
+  end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/nest/nst3.m	Sun Mar 03 10:19:36 2019 +0000
@@ -0,0 +1,13 @@
+function fh = nst3 (gval)
+  global g
+  if (nargin > 0)
+    g = gval;
+  end
+  fh = @nst;
+  function r = nst (gval)
+    if (nargin > 0)
+      g = gval;
+    end
+    r = g;
+  end
+end